/*
 * jo:基于jQuery封装,引入jo前要引入jQuery包;
 */
window.jo = {};
//jo初始化
(function () {
    //[Attr] 域名集合
    jo.address = {
        "contextPath": typeof (contextPath) == 'string' ? contextPath : "/",
        "BAM": "/",
        "UMS": "/",
        "PORTAL": "/",
        "STATIC": "/",
        "FS": "/"
    };
    //jo常量
    jo.Const = {
        URI_LOGOUT: "sso/logout"//用户注销uri
        , REG_MAIL: /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/	//邮箱验证的正则表达式
        , REG_MOBILEPHONE: /^1[3,4,5,6,7,8,9]\d{9}$/	//手机号的正则表达式
    };
    jo.params = {};//页面参数集
    //[Attr] jt.bIE###是否是IE
    var ie_upto10 = /MSIE \d/.test(navigator.userAgent);
    var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent);
    //[Attr] 是不是IE
    jo.bIE = ie_upto10 || ie_11up;
    //[Attr] IE版本
    jo.IEVersion = jo.bIE && (ie_upto10 ? document.documentMode || 6 : ie_11up[1]);
    //[Attr] 是否是Edge
    jo.bEdge = /Edge\//i.test(navigator.userAgent); //Edge
    //[Attr] 是否是IE6
    jo.bIE6 = /msie 6/i.test(navigator.userAgent);
    //[Attr] 是否是Chrome
    jo.bChrome = /chrome/i.test(navigator.userAgent);
    //[Attr] 是否是FireFox
    jo.bFireFox = /firefox/i.test(navigator.userAgent);
    //[Attr] 是否是Opera
    jo.bOpera = /Opera/i.test(navigator.userAgent);
    //[Func] 判断val是否有效(即:!=null,!='null',!='NULL',!='',!='undefined')
    jo.isValid = function (val) {
        if (typeof (val) == "undefined") {
            return false;
        } else {
            if (val != null && val != "null" && val != "undefined") {
                if (val != "") {
                    return true;
                }
                if (val === 0) {//因为0==""返回的是true,所以在这要对0做处理
                    return true;
                }
            }
        }
        return false;
    };
    //[Func] 判断参数不是undefined
    jo.isNotUndefined = function (val) {
        if (typeof (val) == "undefined") {
            return false;
        }
        return true;
    };
    //[Func] 获取默认值,val有效返回val,def返回def(def无效返回"")
    jo.getDefVal = function (val, def) {
        if (typeof (val) == "number") {
            return val;
        }
        if (typeof (val) == "boolean") {
            return val;
        }
        return jo.isValid(val) ? val : (jo.isValid(def) ? def : "");
    };
    //[Func] 获取jo策略的UUID
    jo.getUUID = function (size) {
        var UUIDLen = size||32;//UUID长度
        // var mm = new Date().getTime().toString();//时间戳
        var mm = "qy";
        var str = "AbC13DE2F56g4hi79JKL0MN8opqR9STU8VwxYZ7aBc6def5GHI4jkl3mnO2PQr1stu0vWXyz";//72个字符
        var len = UUIDLen - mm.length;//计算还差几个长度
        for (var i = 0; i < len; i++) {//循环生成
            mm += str.charAt(Math.floor(Math.random() * 71));//生成0-71的随机数,根据随机数获取对应索引的字符
        }
        return mm;
    };
    //[Func] 获取对象
    jo._ = function (finder) {
        return $(finder);
    };
    //[Func] 获取对象
    window.jo_ = function (finder) {
        return $(finder);
    };
    //[Func] 获取尺寸数字,例如:输入5px,输出5
    jo.getSizeNum = function (nPX) {
        return parseInt(nPX.substring(0, nPX.length - 2));
    };
    //[Func] 解析URL,例如:输入{umsPath}/ums/getXX.action,输出http://www.rookie.com/rookie/ums/getXX.action
    jo.parseUrl = function (sUrl) {
        return jo.getCode(sUrl, jo.address);//sUrl.replace(/\{[a-zA-Z]*Path\}/ig,"http://www.rookie.com/rookie");
    };
    //[Func] 对象转为json字符串
    jo.obj2JsonStr = function (obj) {
        if (!jo.isValid(obj)) {
            if (obj === '') {
                return '""';//空字符串返回空字符串,不返回null
            }
            return null;
        }
        if (typeof (obj) == "object" && !(obj instanceof Array)) {
            var arr = new Array();
            for (var e in obj) {
                if (typeof (obj[e]) != "function") {//排除方法
                    var v = jo.obj2JsonStr(obj[e]);
                    arr.push('"' + e + '":' + v + '');
                }
            }
            return '{' + arr.join(",") + '}';
        } else if (typeof (obj) == "string") {
            return '"' + obj + '"';
        } else if (typeof (obj) == "number") {
            return obj + "";
        } else if (typeof (obj) == "boolean") {//布尔类型
            return obj + "";
        } else if (typeof (obj) == "object" && (obj instanceof Array)) {//数组
            if (obj.length > 0) {
                var str = '[';
                for (var i = 0; i < obj.length; i++) {
                    if (i == 0) {
                        str += jo.obj2JsonStr(obj[i]);
                    } else {
                        str += ',' + jo.obj2JsonStr(obj[i]);
                    }
                }
                str += ']';
                return str;
            } else {
                return "[]";
            }
        } else {
            return {};
        }
    };
    //[Func] xml对象转json
    jo.xmlDocument2Json = function (xmlDoc) {
        var xmlObj = $(xmlDoc);//jq对象化
        var child = xmlObj.children();//一级根节点
        var json = {};
        if (child && child.length > 0) {
            for (var i = 0; i < child.length; i++) {
                var ele = child[i];//节点
                var tagName = ele.tagName;//节点名称
                if (typeof (json[tagName]) == "object" && (json[tagName] instanceof Array)) {//该节点名已存在数组类型的值,添加进去
                    json[tagName].push(jo.xmlDocument2Json(ele));
                } else if (typeof (json[tagName]) != "undefined") {//已经存在该节点名的对象,则改装为数组形式
                    var _arr = new Array();
                    _arr.push(json[tagName]);//将原先的放到数组中
                    _arr.push(jo.xmlDocument2Json(ele));//将当前的也放到数组中
                    json[tagName] = _arr;//更新为数组
                } else {
                    json[tagName] = jo.xmlDocument2Json(ele);
                }
            }
            //读取节点属性
            if (xmlDoc.attributes && xmlDoc.attributes.length && xmlDoc.attributes.length > 0) {
                //var attrsArray = Array.prototype.slice.call(xmlDoc.attributes);
                for (var i = 0; i < xmlDoc.attributes.length; i++) {
                    var attr = xmlDoc.attributes[i];
                    if (typeof (json[attr.name]) == "undefined") {
                        json[attr.name] = attr.value;
                    } else {
                        json["_" + attr.name] = attr.value;//节点中存在同名节点,则以_为前缀
                    }
                }
            }
        } else if (xmlDoc.attributes && xmlDoc.attributes.length && xmlDoc.attributes.length > 0) {
            for (var i = 0; i < xmlDoc.attributes.length; i++) {
                var attr = xmlDoc.attributes[i];
                if (typeof (json[attr.name]) == "undefined") {
                    json[attr.name] = attr.value;
                } else {
                    json["_" + attr.name] = attr.value;//节点中存在同名节点,则以_为前缀
                }
            }
        } else {//不存在子节点和属性,则返回节点内容
            return $(xmlDoc).text();
        }
        return json;
    };
    //[Func] post类型的普通请求:虚拟表单实现
    jo.post = function (sUrl, oData) {
        var temp = document.createElement("form");
        temp.action = jo.parseUrl(sUrl);//解析url
        if (typeof (processRequestData) == "function") {
            oData = processRequestData(oData, "post");//数据加工
        }
        temp.method = "post";
        temp.style.display = "none";
        for (var x in oData) {
            var opt = document.createElement("textarea");
            opt.name = x;
            opt.value = oData[x];
            temp.appendChild(opt);
        }
        document.body.appendChild(temp);
        temp.submit();
        return temp;
    };
    //适配老版本的result结果
    jo.adaptOldResult = function (result) {
        if (result && typeof (result) == 'object' && jo.isValid(result.data) && !Array.isArray(result.data)) {
            result._data = result.data;
            result.data = [result.data];
        }
        return result;
    };
    // postJson
    jo.postJsonAjax = jo.postJson = function (sUrl, data, successFunc, errorFunc) {
        var promise = {
            url: jo.parseUrl(sUrl),
            data: data||{},
            successFunc: successFunc,
            successFuncExec: false,
            errorFunc: errorFunc,
            errorFuncExec: false,
            ajaxFinish: false,
            ajaxFail: false,
            ajaxSuccess: false,
            jqXHR: null,
            textStatus: null,
            err: null,
            result: null,
            // 注册失败回调
            error: function (func) {
                this.errorFunc = func;
                this.callbackError();
                return this;
            },
            // 注册成功回调
            success: function (func) {
                this.successFunc = func;
                this.callbackSuccess();
                return this;
            },
            // 失败回调
            callbackError() {
                if (this.ajaxFail) {// ajax失败
                    if (!this.errorFuncExec) {// 未执行失败回调时才执行, 保证只执行一次
                        console.debug('[postJson] 返回失败,触发error回调', this.result);
                        if (typeof this.errorFunc == 'function') {
                            this.errorFunc(this.result||{}, this.textStatus, this.jqXHR, this.err);
                            this.errorFuncExec = true;
                        } else {
                            console.info('[postJson] error回调函数无效');
                        }
                    } else {
                        console.info('[postJson] error回调函数被重复调用');
                    }
                }
            },
            // 成功回调
            callbackSuccess() {
                if (this.ajaxSuccess) {// ajax成功
                    if (!this.successFuncExec) {// 未执行回调时才执行, 保证只执行一次
                        if (typeof this.successFunc == 'function') {
                            this.successFunc(this.result, this.textStatus, this.jqXHR);
                            this.successFuncExec = true;
                        } else {
                            console.info('[postJson] success回调函数无效');
                        }
                    } else {
                        console.info('[postJson] success回调函数被重复调用');
                    }
                }
            },
            // 执行
            run: function () {
                var _this = this;
                $.ajax({
                    type: "POST",
                    url: _this.url, data: typeof(_this.data) == 'object' ? JSON.stringify(_this.data) : _this.data,
                    async: true, cache: false, processData: false,
                    contentType: 'application/json',//数据提交类型
                    datatype: "json", //返回值类型
                    beforeSend: function () {//加载前执行函数

                    },
                    success: function (result, textStatus, jqXHR) {
                        // 处理
                        _this.jqXHR = jqXHR;
                        _this.textStatus = textStatus;
                        _this.result = result;
                        // 成功回调
                        if (result && result.code == 0) {
                            _this.ajaxSuccess = true;
                            _this.callbackSuccess();
                        } else {
                            _this.ajaxFail = true;
                            _this.callbackError();
                        }

                    },
                    complete: function (jqXHR, textStatus) {
                        if (jo.isValid(jqXHR.responseText)) {//后台传的json字符串
                            var re = '';
                            try {
                                re = JSON.parse(jqXHR.responseText);
                            } catch (err) {
                                console.error('[postJson] 解析返回结果异常', err);
                            }
                            if (jo.isValid(re) && re.code == '-101') {//session过期异常代码
                                console.info('[postJson] 异常代码[-101]:会话过期,url=%s,返回结果=%o', sUrl, re);
                                if (re.redirectTo) {
                                    top.window.location.href = re.redirectTo;
                                } else {
                                    var logoutUrl = jo.getDefVal(URL_UMS, contextPath) + jo.Const.URI_LOGOUT;//登出
                                    logoutUrl += jo.getLinkSign(logoutUrl) + "fromUrl=" + encodeURIComponent(top.location.href);
                                    top.window.location.href = logoutUrl;//登出
                                }
                            }
                        } else {

                        }
                    },
                    error: function (jqXHR, textStatus, err) {
                        //jqXHR:jQuery增强的XHR
                        //textStatus:请求完成状态
                        //err:底层通过throw抛出的异常对象，类型与值与错误类型有关
                        console.error("[postJson] --ajax请求出错-参数打印...--" + "\n" +
                            "【请求地址】" + this.url + "\n" +
                            "【请求类型】" + this.type + "\n" +
                            "【请求参数】" + this.data + "\n" +
                            "【返回状态】" + jqXHR.status + "\n" +
                            "【返回类型】" + this.datatype + "\n");
                        // 处理失败
                        _this.ajaxFail = true;
                        _this.jqXHR = jqXHR;
                        _this.textStatus = textStatus;
                        _this.err = err;
                        // 调用失败回调
                        _this.callbackError();
                    }
                });
                return _this;
            }
        };
        return promise.run();
    };
    //[Func] post类型的ajax请求
    jo.postAjax = function (sUrl, sData, func, isAsync) {
        console.error('postAjax已过时,推荐使用postJson');
        sUrl = jo.parseUrl(sUrl);//解析url
        if (typeof (processRequestData) == "function") {
            sData = processRequestData(sData, "postAjax");//数据加工
        }
        //判断是否跨域
        /*if(sUrl.indexOf("http://") == 0 || sUrl.indexOf("https://") == 0){//url为完整形式时继续判断是否跨域
			var cur = location.protocol + "//" + location.host;//当前页面的域名
			if(sUrl.indexOf(cur) != 0){//如果请求地址不是以当前页面的域名开头,则认为是跨域请求
				jo.postJSONP(sUrl, sData, func);//调用跨域请求
				return;
			}
		}*/
        if (!jo.isValid(isAsync)) {
            isAsync = false;//默认非异步加载
        }
        if (!sData) {
            sData = null;
        } else {
            if (typeof (sData) == "string") {//字符串,key=value形式

            } else if (typeof (sData) == "object") {//对象,{key:value,key:value}形式
                //转换为1=1&2=2形式字符串
                var str = "";

                for (var k in sData) {
                    //console.info(sData[k]+"----"+encodeURIComponent(sData[k]))
                    str += "&" + k + "=" + encodeURIComponent(sData[k]);
                }
                //replace(/\+/g, '%2B') 处理加号传到服务端变成空格的问题
                sData = str.substring(1);//.replace(/\+/g, '%2B');
            }
        }
        var res = null;
        $.ajax({
            type: "POST",
            url: sUrl,
            data: sData,
            async: isAsync,
            cache: false,
            contentType: 'application/x-www-form-urlencoded',//'application/json',数据提交类型
            processData: false,
            datatype: "json", //返回值类型
            beforeSend: function () {//加载前执行函数

            },
            success: function (result, textStatus, jqXHR) {
                if (jo.isValid(result) && result.code != '-101' /*&& result.code != '-102' && result.code != '-103' && result.code != '-901' && result.code != '-999'*/) {
                    result = jo.adaptOldResult(result);
                    if (typeof (func) == "function") {
                        try {
                            func(result)
                        } catch (err) {
                        }
                        ;
                    }
                    res = result;
                } else {
                    res = null;
                }
            },
            complete: function (jqXHR, textStatus) {
                if (jo.isValid(jqXHR.responseText)) {//后台传的json字符串
                    var re = '';
                    try {
                        re = JSON.parse(jqXHR.responseText);
                    } catch (err) {
                        if (textStatus == "error") {

                        }
                    }
                    if (jo.isValid(re) && re.code == '-101') {//session过期异常代码
                        console.info('异常代码[-101]:会话过期,url=%s,返回结果=%o', sUrl, re);
                        /*jo.confirm(re.info,function(){

    		        	});*/
                        if (re.redirectTo) {
                            top.window.location.href = re.redirectTo;
                        } else {
                            var logoutUrl = jo.getDefVal(URL_UMS, contextPath) + jo.Const.URI_LOGOUT;//登出
                            /*var fromUrl = window.location.href;
							var win = window;
							while(win != top){
								var parentUrl = win.parent.location.href;//父页面的url
								fromUrl = parentUrl + jo.getLinkSign(parentUrl) + "childUrl=" + encodeURIComponent(fromUrl);
								win = win.parent;
							}*/
                            logoutUrl += jo.getLinkSign(logoutUrl) + "fromUrl=" + encodeURIComponent(top.location.href);
                            top.window.location.href = logoutUrl;//登出
                        }
                        //top.window.location.href ="page/jsp/login.jsp";  //跳转页面
                    }/*else if(jo.isValid(re) && re.code == '-102'){//平台业务异常代码
						jo.showMsg(jo.getDefVal(re.info,"非法操作!"));
					}else if(jo.isValid(re) && re.code == '-801'){//参数非法异常
						jo.showMsg(jo.getDefVal(re.info,"参数异常!"));
					}else if(jo.isValid(re) && re.code == '-901'){//平台业务异常代码
    		    		jo.showMsg(jo.getDefVal(re.info,"*系统异常!"));
    		    	}else if(jo.isValid(re) && re.code == '-999'){//系统异常代码
    		    		jo.showMsg("*系统出现异常,请与管理员联系!");
    		    	}else{

    		    	}*/
                } else {

                }
            },
            error: function (jqXHR, textStatus, err) {
                //jqXHR:jQuery增强的XHR
                //textStatus:请求完成状态
                //err:底层通过throw抛出的异常对象，类型与值与错误类型有关
                console.log(arguments);
                console.error(" --ajax请求出错-参数打印...--" + "\n" +
                    "【请求地址】" + this.url + "\n" +
                    "【请求类型】" + this.type + "\n" +
                    "【请求参数】" + this.data + "\n" +
                    "【返回状态】" + jqXHR.status + "\n" +
                    "【返回类型】" + this.datatype + "\n");
            },
            statusCode: {
                '403': function (jqXHR, textStatus, err) {
                    //jqXHR:jQuery增强的XHR
                    //textStatus:请求完成状态
                    //err:底层通过throw抛出的异常对象，类型与值与错误类型有关
                    console.log(arguments);
                    console.log(403);
                },
                '400': function () {
                    console.log(400);
                },
                '404': function () {
                    console.log(404 + ":阿偶,无法链接到二次元资源:" + this.url);
                }
            }
        });
        return res;
    };
    //[Func] 获取地址栏参数
    jo.getUrlParam = function (key) {
        var reg = new RegExp("(^|&)" + key + "=([^&]*)(&|$)", "i");
        var r = window.location.search.substr(1).match(reg);
        //decodeURI(escape(unescape(r[2])))处理中文乱码
        if (r != null) {
            if (jo.bIE) {
                return r[2];
            } else {
                return decodeURI(escape(unescape(r[2])));
            }
        }
        return "";
    };
    //[Func] 获取所有地址栏参数
    jo.getUrlParams = function (sUrl) {
        var searchStr = jo.getDefVal(sUrl, window.location.search);
        if (searchStr.indexOf("?") > -1) {//有参数
            searchStr = searchStr.substring(1);
            var searchs = searchStr.split("&");
            var res = {};
            for (var i = 0; i < searchs.length; i++) {
                var j = searchs[i].indexOf("=");
                var skey = searchs[i].substring(0, j);
                var svalue = searchs[i].substring(j + 1);
                res[skey] = svalue;
            }
            return res;
        } else {
            return {};
        }
    };
    //[Func] 页面弹出提示信息
    jo.showMsg = function (msg) {
        jo.closeMsg();
        var msg_box = "<div style='width:20%;height:auto;position:absolute;z-index:9999;top:40%;left:40%;background-color:#333333;line-height:25px; letter-spacing:1px;padding:3px 5px;font-size:16px;color:#FFFFFF;text-align:center;display:none;' class='showMsg_style' id='showMsg' onclick='jo.closeMsg()'>" + msg + "</div>";
        $("body").append(msg_box);
        $("#showMsg").fadeIn(1000).fadeOut(3000);
    };
    //[Func] 关闭提示信息
    jo.closeMsg = function () {
        if ($("#showMsg")) {//存在提示消息窗
            $("#showMsg").remove();
        }
    };
    //[Func] 正在加载提示框,当传入false表示关闭
    jo.showLoading = function (msgOrClose) {
        //当参数为false时,关闭提示
        if (typeof (msgOrClose) == "boolean" && !msgOrClose) {
            jo.remove(document.getElementById("joMsg_loading"));
        } else {
            var obj = document.getElementById("joMsg_loading");
            if (jo.isValid(obj)) {
                $(obj).html(jo.getDefVal(msgOrClose, "正在加载..."));
            } else {
                var _html = '<div class="joMsg_loading" id="joMsg_loading">';
                _html += jo.getDefVal(msgOrClose, "正在加载...");
                _html += '</div>';
                $("body").append(_html);
            }
        }
    };
    //[Func] 将表单数据转为json对象
    jo.form2Json = function (id) {
        var arr = null;
        if (typeof (id) == "string") {//传入字符串,说明是表单的ID
            if (!($("#" + id).attr("id"))) {
                return null;
            }
            arr = $("#" + id).serializeArray();
        } else if (typeof (id) == "object") {
            arr = id.serializeArray();
        } else {
            return null;
        }

        //直接返回js对象
        var json = {};
        for (var i = 0; i < arr.length; i++) {
            var val = arr[i].value;
            //在这里不需要对换行做处理,否则会将换行符变为\r\n字符串
            /*if(val){
				val = val.replace(/\r/ig, "\\r");
				val = val.replace(/\n/ig, "\\n");
			}*/
            if (json[arr[i].name]) {//已存在该name,例如:多选框,默认逗号拼接
                json[arr[i].name] = json[arr[i].name] + ',' + val;
            } else {
                json[arr[i].name] = val;
            }
        }
        return json;
    };
    //[Func] 将表单数据转为json字符串
    jo.form2JsonStr = function (id) {
        return JSON.stringify(jo.form2Json(id));
    };
    //[Func] 将表单数据转为key1=value1&key2=value2..形式
    jo.form2ParamStr = function (id) {
        if (!($("#" + id).attr("id"))) {
            return "";
        }
        return $("#" + id).serialize();
    };
    //[Func] 重置表单,传入表单id
    jo.reset = function (id) {
        document.getElementById(id).reset();
    };
    //[Func] 全选/取消全选,全选框的id为checkAll,参数为复选框的name(默认是ID);参数obj表示全选框对象,通常用this;示例:<input type="checkbox" onclick="jo.checkAll('ID',this)">
    jo.checkAll = function (name, obj) {
        var _id = name ? name : "ID";
        if (typeof (obj) == "object") {
            $("input[name='" + _id + "']").prop("checked", jo.getDefVal($(obj).prop("checked"), false));//1.9以上jquery使用
        } else {
            $("input[name='" + _id + "']").prop("checked", jo.getDefVal($("#checkAll").prop("checked"), false));//1.9以上jquery使用
        }
    };
    //[Func] 得到所有选中复选框的值:参数为checkbox的name;返回以逗号间隔的字符串
    jo.getCheckBoxValue = function (boxName) {
        if (!boxName) {
            boxName = "ID";
        }
        var ids = $("input[name='" + boxName + "']" + ":checked");
        idStr = "";
        //获取勾选集
        for (var i = 0; i < ids.length; i++) {
            idStr += "," + ids[i].value;
        }
        idStr = idStr.substring(1);
        return idStr;
    };
    // 获取元素定位, 原生元素
    jo.getXY = function(ele) {
        var left = ele.offsetLeft;
        var top = ele.offsetTop;
        // 取得元素的offsetParent
        var parent = ele.offsetParent;

        // 一直循环直到根元素
        while(parent) {
            left += parent.offsetLeft;
            top += parent.offsetTop;
            parent = parent.offsetParent;
        }
        return {
            x: left,
            y: top
        };
    };
    // 获取定位详情
    jo.getPositionDetail = function(e, classNameOrFunc) {
        // 鼠标位置
        var x = e.pageX;
        var y = e.pageY;
        // 找到按钮放置盒子
        var target = e.target;
        if (classNameOrFunc) {
            if (typeof classNameOrFunc == 'function') {
                while (target && !classNameOrFunc(target)) {
                    target = target.parentNode;
                }
            } else {
                while (target && !$(target).hasClass(classNameOrFunc)) {
                    target = target.parentNode;
                }
            }
        }
        // console.log('[获取事件坐标详情] 查找真正的事件节点:', target)
        // 元素的位置
        var xy = jo.getXY(target);
        var eleX = xy.x;
        var eleY = xy.y;
        // 元素的宽高
        var w = target.offsetWidth;
        var h = target.offsetHeight;

        // 判断是否放到右面
        var right = x > (eleX + w / 2);
        // 是否下面
        var down = y > (eleY + h / 2);
        return {
            // 预期事件对象, 比如事件在父亲上挂着, 操作孩子时也会触发, 此时eventTarget是孩子(event事件对象的target), target是父亲, 也就是我们预期的那个元素
            target: target,
            // 事件触发对象
            eventTarget: e.target,
            // 预期对象的x坐标
            targetX: eleX,
            // 预期对象的y坐标
            targetY: eleY,
            // 预期对象的宽度, 含width,padding,border
            targetWidth: w,
            // 预期对象的高度, 含height,padding,border
            targetHeight: h,
            // 触发事件时鼠标x坐标
            x: x,
            // 触发事件时鼠标y坐标
            y: y,
            // 鼠标是否在预期对象左边
            atLeft: !right,
            // 鼠标是否在预期对象右边
            atRight: right,
            // 鼠标是否在预期对象上边
            atUp: !down,
            // 鼠标是否在预期对象下边
            atDown: down
        };
    };
    //[Func] 获得url添加条件的连接符,即判断传入的URL后方链接新参数应该用'?'还是用'&'
    jo.getLinkSign = function (str) {
        if (str.indexOf("?") >= 0) {//传进来的URL带有'?'
            if (str.indexOf("?") + 1 == str.length) {//当url以?结尾时,返回''
                return "";
            }
            if (str.indexOf("&") + 1 == str.length) {//当url以&结尾,返回''
                return '';
            } else {
                if (str.split("?").length > 2) {//有且只有1个'?'
                    console.info("URL格式可能存在异常:" + str);
                }
                return "&";
            }
        } else {
            return "?";
        }
    };
    //[Func] 格式化日期 : 毫秒数/date==>yyyy-MM-dd
    jo.formatDate = function (ms) {
        if (!jo.isValid(ms)) {
            return "";
        }
        if (typeof (ms) == "string" && !isNaN(ms)) {
            ms = parseInt(ms);
        }
        var _ms = jo.getDefVal(ms);
        var myDate = new Date(_ms);//生成时间类型
        var yyyy = myDate.getFullYear();//年份 xxxx
        var MM = (myDate.getMonth() + 1) < 10 ? "0" + (myDate.getMonth() + 1) : (myDate.getMonth() + 1);//月份 0-11,加1就是真实月份
        var dd = myDate.getDate() < 10 ? "0" + myDate.getDate() : myDate.getDate();//日期 1-31
        //myDate.getDay();//星期几 0-6 ,0代表星期天
        return yyyy + "-" + MM + "-" + dd;
    };
    //[Func] 格式化时间 : 毫秒数/date==>yyyy-MM-dd hh:mm:ss
    jo.formatTime = function (ms) {
        if (!jo.isValid(ms)) {
            return "";
        }
        if (typeof (ms) == "string" && !isNaN(ms)) {
            ms = parseInt(ms);
        }
        var _ms = jo.getDefVal(ms);
        var myDate = new Date(_ms);//生成时间类型
        var yyyy = myDate.getFullYear();//年份 xxxx
        var MM = (myDate.getMonth() + 1) < 10 ? "0" + (myDate.getMonth() + 1) : (myDate.getMonth() + 1);//月份 0-11,加1就是真实月份
        var dd = myDate.getDate() < 10 ? "0" + myDate.getDate() : myDate.getDate();//日期 1-31
        //时分秒小于10时在前补0
        var hh = myDate.getHours() < 10 ? "0" + myDate.getHours() : myDate.getHours();//小时数
        var mm = myDate.getMinutes() < 10 ? "0" + myDate.getMinutes() : myDate.getMinutes();//分钟数
        var ss = myDate.getSeconds() < 10 ? "0" + myDate.getSeconds() : myDate.getSeconds();//秒数
        return yyyy + "-" + MM + "-" + dd + " " + hh + ":" + mm + ":" + ss;
    };
    //[Func] 格式化时间戳 : 毫秒数/date==>yyyy-MM-dd hh:mm:ss.SSS
    jo.formatTimestamp = function (ms) {
        if (!jo.isValid(ms)) {
            return "";
        }
        if (typeof (ms) == "string" && !isNaN(ms)) {
            ms = parseInt(ms);
        }
        var _ms = jo.getDefVal(ms);
        var myDate = new Date(_ms);//生成时间类型
        var yyyy = myDate.getFullYear();//年份 xxxx
        var MM = (myDate.getMonth() + 1) < 10 ? "0" + (myDate.getMonth() + 1) : (myDate.getMonth() + 1);//月份 0-11,加1就是真实月份
        var dd = myDate.getDate() < 10 ? "0" + myDate.getDate() : myDate.getDate();//日期 1-31
        //时分秒小于10时在前补0
        var hh = myDate.getHours() < 10 ? "0" + myDate.getHours() : myDate.getHours();//小时数
        var mm = myDate.getMinutes() < 10 ? "0" + myDate.getMinutes() : myDate.getMinutes();//分钟数
        var ss = myDate.getSeconds() < 10 ? "0" + myDate.getSeconds() : myDate.getSeconds();//秒数
        var ms = myDate.getMilliseconds() < 10 ? "00" + myDate.getMilliseconds() : (myDate.getMilliseconds() < 100 ? "0" + myDate.getMilliseconds() : myDate.getMilliseconds());//毫秒数
        return yyyy + "-" + MM + "-" + dd + " " + hh + ":" + mm + ":" + ss + "." + ms;
    };
    //[Func] 执行代码,参数1:代码表达式string,参数2:表达式中的常量(类似于{URL_UMS}格式)映射,object类型
    jo.execCode = function (sCode, paramArr) {
        if (jo.isValid(sCode)) {//待执行代码的有效性
            return eval(jo.getCode(sCode, paramArr));
        } else {
            return false;
        }
    };
    //[Func] 解析代码
    jo.getCode = function (sCode, paramArr) {
        if (jo.isValid(sCode)) {//待执行代码的有效性
            if (jo.isValid(paramArr)) {//替换参数有效的话,执行替换,否则直接返回sCode
                //替换变量
                var reg = /\{[a-zA-Z0-9_]*\}/g;
                if (reg.test(sCode)) {
                    var a = sCode.match(reg);
                    if (!jo.isValid(paramArr)) {
                        //参数替换数组无效
                    } else {
                        for (var i = 0; i < a.length; i++) {
                            var x = a[i] + "";
                            var key = x.substring(1, x.length - 1);
                            sCode = sCode.replace(x, paramArr[key]);
                        }
                    }
                }
            }
            return sCode;
        } else {
            return "";
        }
    };
    //[Func] 解析表达式, 参数1:表达式(字段/{字段}/[=方法({字段})]/文字), 参数2:数据对象, 参数3:当前索引
    jo.parseExpression = function (expression, item, m) {
        var _val = expression;//
        //对可执行字段进行处理
        if (jo.isValid(_val)) {
            //以 [= 开头, ] 结尾的格式,表示调用函数对字段进行处理,其中的变量(即该行json的字段值)要使用花括号括起来,例如 [=jo.formatDate('{time}')],time为从数据库查询出来的字段
            if (_val && /^\[=(.)*\]$/.test(_val)) {
                //去掉无效的[=和]
                _val = _val.substring(2, _val.length - 1);
                //解析{xx}类型的变量
                _val = jo.getCode(_val, item);
                //执行该函数
                _val = eval(_val);
            } else if (/\{(.)*\}/g.test(_val)) {//解析{xx}类型的变量
                _val = jo.getCode(_val, item);
            } else if (_val && _val == "_seq") {//序号常量,自动生成序号
                _val = m + 1;
            } else {
                _val = jo.getDefVal(item[_val], "");//正常字段名则从json中取对应字段值
            }
        }
        return jo.getDefVal(_val, "");//最后对_val进行检查,无效则赋值空格
    };
    //[Func] 初始化按钮权限
    jo.initBtnAuth = function () {
        $(".joBtn[isShow]").each(function (index, element) {
            var isShow = $(this).attr("isShow");
            //当返回false或'false'时才隐藏,isShow为空时也隐藏
            if (jo.execCode(isShow) == false || jo.execCode(isShow) == 'false') {
                $(this).hide();
            } else {
                $(this).show();
            }
        });
    };
    //[Func] 加载css,参数sUrl为css地址
    jo.loadCSS = function (sUrl) {
        if (jo.isValid(sUrl)) {
            var link = document.createElement("link");
            link.setAttribute("rel", "stylesheet");
            link.setAttribute("href", sUrl);
            document.head.appendChild(link);
        }
    };

    //[Func] 加载JavaScript,参数sUrl为js地址
    jo.loadJS = function (sUrl) {
        if (jo.isValid(sUrl)) {
            var script = document.createElement("script");
            script.setAttribute("type", "text/javascript");
            script.setAttribute("charset", "utf-8");
            script.setAttribute("src", sUrl);
            document.head.appendChild(script);
        }
    };
    //JSONP回调方法数组
    window.joJSONP = new Array();
    //[Func] JSONP跨域请求
    jo.postJSONP = function (sUrl, sData, callback) {
        if (jo.isValid(sUrl)) {
            console.log("jsonp: " + sUrl);
            if (typeof (sData) == "string") {//字符串,key=value形式

            } else if (typeof (sData) == "object") {//对象,{key:value,key:value}形式
                //转换为1=1&2=2形式字符串
                var str = "";
                for (var k in sData) {
                    str += "&" + k + "=" + encodeURIComponent(sData[k]);//sData[k];
                }
                //replace(/\+/g, '%2B') 处理加号传到服务端变成空格的问题
                sData = str.substring(1);//.replace(/\+/g, '%2B');
            }
            var idx = joJSONP.push(callback) - 1;
            sUrl += jo.getLinkSign(sUrl) + "callback=joJSONP%5B" + idx + "%5D";
            if (sData) {
                sUrl += "&" + sData;
            }
            jo.loadJS(sUrl);
        }
    };

    //[Func] 移除某元素,参数为dom元素对象
    jo.remove = function (element) {
        if (jo.isValid(element)) {
            var pElement = element.parentNode;
            pElement.removeChild(element);
        }
    };

    //[Func] iframe加载完成事件处理
    jo.iframeLoaded = function (iframe, func) {
        if (iframe.attachEvent) {
            iframe.attachEvent("onload", func);
        } else {
            iframe.onload = func;
        }
    };

    //[Func] 生成遮罩层,返回遮罩层元素,引用的jo窗口相关css
    jo.getShade = function (zIndex) {
        var shadeElement = document.createElement("div");
        //遮罩层ID为窗口名字+Shade
        shadeElement.setAttribute("id", "joShade");
        //遮罩层样式为joWinShade
        shadeElement.setAttribute("class", "joWinShade");
        if (jo.isValid(zIndex)) {
            //z-index属性
            shadeElement.style.zIndex = zIndex;
        }
        document.body.appendChild(shadeElement);
        return shadeElement;
    };
    //[Func] 打开新窗口
    jo.newWindow = function (sUrl) {
        sUrl = jo.parseUrl(sUrl);//解析url
        window.open(sUrl);
    };

    /**
     * [Func] 表单验证,参数为表单jQuery对象
     * 1.ErrEmpty 为空提示,含有此属性说明该项为必填项,属性值为提示信息;
     * 2.ErrLength 长度验证,值为该项允许的最大长度;
     * 3.ErrInfo 错误提示,发生验证错误时的提示信息;
     * 4.ErrReg 验证的正则表达式,可以自定义;
     * 5.ErrMail 邮箱验证
     * 6.ErrPhone 手机号验证
     */
    jo.checkForm = function (oForm) {
        //必填/非空验证
        var emptys = oForm.find("input[ErrEmpty]");
        for (var i = 0; i < emptys.length; i++) {
            var e = emptys[i];
            var val = $(e).val();
            if (val == "") {
                jo.showMsg(jo.getDefVal($(e).attr("ErrEmpty"), "该项不可以为空！"));//提示信息
                $(e).focus();//获取焦点
                return false;
            }
        }
        //select下拉列表的非空验证
        emptys = oForm.find("select[ErrEmpty]");
        for (var i = 0; i < emptys.length; i++) {
            var e = emptys[i];
            var val = $(e).val();
            if (val == "") {
                jo.showMsg(jo.getDefVal($(e).attr("ErrEmpty"), "该项不可以为空！"));//提示信息
                $(e).focus();//获取焦点
                return false;
            }
        }
        //长度验证
        var lengths = oForm.find("input[ErrLength]");
        for (var i = 0; i < lengths.length; i++) {
            var e = lengths[i];
            var len = $(e).attr("ErrLength");//限制长度
            var val = $(e).val();//值
            //alert(len+"--"+val);
            if (jo.isValid(len) && jo.isValid(val)) {//长度和值均有效时,判断是否超过长度
                if (val.length > parseInt(len)) {//长度超过
                    jo.showMsg(jo.getDefVal($(e).attr("ErrInfo"), "该项超过最大长度[" + len + "]！"));//提示信息
                    $(e).focus();//获取焦点
                    return false;
                }
            }
        }

        //数字验证
        var numbers = oForm.find("input[ErrNumber]");
        for (var i = 0; i < numbers.length; i++) {
            var e = numbers[i];
            //var len = $(e).attr("ErrLength");//限制长度
            var val = $(e).val();//值
            if (jo.isValid(val)) {//值有效时,判断是否是数字类型
                if (isNaN(val)) {
                    jo.showMsg(jo.getDefVal($(e).attr("ErrNumber"), "该值必须是数字类型！"));//提示信息
                    $(e).focus();//获取焦点
                    return false;
                }
            }
        }

        //邮箱验证
        var numbers = oForm.find("input[ErrMail]");
        for (var i = 0; i < numbers.length; i++) {
            var e = numbers[i];//表单元素
            var val = $(e).val();//值
            if (jo.isValid(val)) {//值有效时,进行验证
                if (!jo.Const.REG_MAIL.test(val)) {
                    jo.showMsg(jo.getDefVal($(e).attr("ErrMail"), "错误的邮箱格式！"));//提示信息
                    $(e).focus();//获取焦点
                    return false;
                }
            }
        }

        //手机号验证
        var numbers = oForm.find("input[ErrPhone]");
        1
        for (var i = 0; i < numbers.length; i++) {
            var e = numbers[i];//表单元素
            var val = $(e).val();//值
            if (jo.isValid(val)) {//值有效时,进行验证
                if (!jo.Const.REG_MOBILEPHONE.test(val)) {
                    jo.showMsg(jo.getDefVal($(e).attr("ErrPhone"), "错误的手机号格式！"));//提示信息
                    $(e).focus();//获取焦点
                    return false;
                }
            }
        }

        //自定义正则表达式
        var numbers = oForm.find("input[ErrReg]");
        for (var i = 0; i < numbers.length; i++) {
            var e = numbers[i];//表单元素
            var val = $(e).val();//值
            var reg = $(e).attr("ErrReg");
            if (jo.isValid(val) && jo.isValid(reg)) {//值有效时,进行验证
                if (!new RegExp(reg).test(val)) {
                    jo.showMsg(jo.getDefVal($(e).attr("ErrInfo"), "验证未通过！"));//提示信息
                    $(e).focus();//获取焦点
                    return false;
                }
            }
        }


        return true;
    };

    //[Func] confirm选择框
    jo.confirm = function (sMsg, funcOk) {
        if (window.confirm(sMsg)) {
            funcOk();
        }
    };

    /**
     * [Func] 数组排序,基于冒泡排序算法
     * @param arr 待排序数组
     * @param func 核心比较回调函数(参考java中Comparator比较器),回调函数有2参数,返回元素a和b的比较结果,返回true表示元素a在b前
     */
    jo.sort = function (arr, func) {
        for (var i = 0; i < arr.length; i++) {
            for (var j = i + 1; j < arr.length; j++) {
                //两者比较,返回true则a在b前面,返回false则b在a前面
                if (!func(arr[i], arr[j])) {
                    var temp = arr[i];
                    arr[i] = arr[j];
                    arr[j] = temp;
                }
            }
        }
    };

    /**
     * 智能解析key,和execCode类似
     * 1.key可以是普通字符串,例如name,返回oObj对象的name属性
     * 2.key可以是多属性拼接表达式,例如{id}({name}),传入该值返回的是{id}和{name}分别被id和name属性替换后的值==> id的值(name的值)
     * 3.key可以是有返回值的方法表达式,例如:[=format({time})],返回的是time属性传入format方法后返回的值
     * @param oObj
     * @param sKey
     */
    jo.parseKey = function (oObj, sKey) {
        var _val = sKey;
        //对可执行字段进行处理
        if (jo.isValid(_val)) {
            //以 [= 开头, ] 结尾的格式,表示调用函数对字段进行处理,其中的变量(即该行json的字段值)要使用花括号括起来,例如 [=jo.formatDate('{time}')],time为从数据库查询出来的字段
            if (_val && /^\[=(.)*\]$/.test(_val)) {
                //去掉无效的[=和]
                _val = _val.substring(2, _val.length - 1);
                //解析{xx}类型的变量
                _val = jo.getCode(_val, oObj);
                //执行该函数
                _val = eval(_val);
            } else if (/\{(.)*\}/g.test(_val)) {//解析{xx}类型的变量
                _val = jo.getCode(_val, oObj);
            } else {
                _val = jo.getDefVal(oObj[_val], "");//正常字段名则从接送中取对应字段值
            }
        }
        return jo.getDefVal(_val, "");//最后对_val进行检查,无效则赋值空
    };
    /**
     * 将只读属性的表单元素转为文本
     * @param formId 表单id
     */
    jo.readonly2Label = function (formId) {
        formId = formId || "pageForm";
        var form = $("#" + formId);//表单
        //处理input
        form.find("input[type='text'][readonly]").each(function (idx, ele) {
            var val = $(ele).val();
            var label = '<span>' + val + '</span>';
            $(ele).after(label);//在元素后加文本
            jo.remove(ele);//删除元素
        });
        //处理select
        form.find("select[readonly]").each(function (idx, ele) {
            var text = "";
            if (typeof (ele.options[ele.options.selectedIndex]) != "undefined") {//避免因异步问题或空值带来的undefind错误
                text = ele.options[ele.options.selectedIndex].text;
            }
            var label = '<span>' + text + '</span>';
            $(ele).after(label);
            jo.remove(ele);
        });
        //处理textarea
        form.find("textarea[readonly]").each(function (idx, ele) {
            var val = $(ele).val();
            var label = '<span>' + val + '</span>';
            $(ele).after(label);
            jo.remove(ele);
        });
    };
    /**
     * 表单元素转label
     * @param formId
     */
    jo.form2Label = function (formId) {
        formId = formId || "pageForm";
        var form = $("#" + formId);//表单
        //处理input
        form.find("input[type='text']").each(function (idx, ele) {
            var val = $(ele).val();
            var label = '<span>' + val + '</span>';
            $(ele).after(label);//在元素后加文本
            jo.remove(ele);//删除元素
        });
        //处理select
        form.find("select").each(function (idx, ele) {
            var text = "";
            if (typeof (ele.options[ele.options.selectedIndex]) != "undefined") {//避免因异步问题或空值带来的undefind错误
                text = ele.options[ele.options.selectedIndex].text;
            }
            var label = '<span>' + text + '</span>';
            $(ele).after(label);
            jo.remove(ele);
        });
        //处理textarea
        form.find("textarea").each(function (idx, ele) {
            var val = $(ele).val();
            var label = '<span>' + val + '</span>';
            $(ele).after(label);
            jo.remove(ele);
        });
    };

    // 数组转对象
    jo.array2Object = function(arr, keyFunc, valFunc) {
        var obj = {};
        if (arr) {
            for (var i=0;i<arr.length;i++) {
                var item = arr[i];
                var key = item;
                if (typeof keyFunc == 'function') {
                    key = keyFunc(item);
                }
                var val = item;
                if (typeof valFunc == 'function') {
                    val = valFunc(item);
                }
                obj[key] = val;
            }
        }
        return obj;
    };
    // 对象转数组
    jo.object2Array = function(obj, elementFunc) {
        var arr = [];
        if (obj) {
            for (var k in obj) {
                if (typeof elementFunc == 'function') {
                    arr.push(elementFunc(k, obj[k]));
                } else {
                    arr.push(k);
                }
            }
        }
        return arr;
    };
    // 拷贝对象
    jo.copyObject = function (obj) {
        var newObj = {};
        if (obj) {
            for (var k in obj) {
                newObj[k] = obj[k];
            }
        }
        return newObj;
    };
    // 深拷贝对象
    jo.copyObjectDeep = function (obj) {
        return JSON.parse(JSON.stringify(obj));
    };
    // 合并对象, 返回新对象
    jo.mergeObject = function (one, two) {
        var newObj = {};
        if (one) {
            for (var k in one) {
                newObj[k] = one[k];
            }
        }
        if (two) {
            for (var k in two) {
                newObj[k] = two[k];
            }
        }
        return newObj;
    };
    // 拷贝数组
    jo.copyArray = function (arr) {
        var newArr = [];
        if (arr) {
            for (var i = 0; i < arr.length; i++) {
                newArr.push(arr[i]);
            }
        }
        return newArr;
    };
    // 合并数组, 返回合并后的新数组
    jo.mergeArray = function (arrOne, arrTwo) {
        var arr = [];
        if (arrOne) {
            for (var i = 0; i < arrOne.length; i++) {
                arr.push(arrOne[i]);
            }
        }
        if (arrTwo) {
            for (var i = 0; i < arrTwo.length; i++) {
                arr.push(arrTwo[i]);
            }
        }
        return arr;
    };
    // 数组循环
    jo.forEach = function (arr, forFunc) {
        if (arr && arr.length > 0) {
            for (var i = 0; i < arr.length; i++) {
                var item = arr[i];
                forFunc(item, i);
            }
        }
    };
    // 数组转数组
    jo.array2array = function (arr, mapFunc) {
        var newArr = [];
        if (arr && arr.length > 0) {
            for (var i = 0; i < arr.length; i++) {
                var item = arr[i];
                newArr.push(mapFunc(item, i));
            }
        }
        return newArr;
    };
    // 日志输出
    jo.log = function (info, args) {
        console.log(info + ', 输出内容=%o', args);
    };
})();
//jo初始化完成

//jo树操作初始化
(function () {
    /**
     * jo树的展示,对于zTree相关封装
     * 参数说明:
     * treeId:树的id
     * zNodes:节点集信息
     * setting:zTree树的相关设置(为空则使用默认设置)
     * onClickFunc:树节点的点击事件
     * asyn:异步加载标识,0和1
     * onAsynSuccess:异步加载完成后的回调
     * childUrl:子节点的url
     * autoParam:查询子节点时携带的参数(详情参见zTree API)
     * txt_id:回显ID值的框的id
     * txt_name:回显NAME值的框的id
     * field_id:回显ID的字段名,与txt_id对应;--默认为item_id
     * field_name:回显NAME的字段名,与txt_name对应;--默认为item_show
     * item_id:节点的id字段名
     * item_pid:节点的父id字段名(控制层级)
     * item_show:节点显示文本的字段名
     * item_title:节点的提示信息字段名;--默认为item_show
     *
     * check:树勾选配置
     * edit:树编辑配置
     * initZTreeBefore:初始化ztree树的前置处理,参数1为树id,参数2为setting,参数3为nodes
     *
     */
    //[Attr] 页面内zTree树的信息,存放了回显信息等,可以在需要的时候调用,参数key参见注释
    jo.zTrees = {};
    //[Func] 渲染zTree(简单zTree)
    jo.drawZTree = function (oParams) {
        if (typeof (oParams) == "object") {//树参数有效
            var _treeId = jo.getDefVal(oParams.treeId, "zTree");
            var _zNodes = {};
            if (jo.isValid(oParams.zNodes)) {
                _zNodes = oParams.zNodes;
            } else {
                console.log("树参数异常:zNodes树节点信息无效!");
                return;
            }
            var setting = {};
            if (jo.isValid(oParams.setting)) {
                setting = oParams.setting;
            } else {
                var _itemId = jo.getDefVal(oParams.item_id, "id");
                var _itemPid = jo.getDefVal(oParams.item_pid, "pid");
                var _showField = jo.getDefVal(oParams.item_show, "name");
                var _title = jo.getDefVal(oParams.item_title, _showField);
                var _onClickFunc = typeof (oParams.onClickFunc) == "function" ? oParams.onClickFunc : jo.zTreeOnClick;
                var _asyn = true;//默认异步加载
                if (jo.isValid(oParams.asyn) && (oParams.asyn == "0" || oParams.asyn == "false" || oParams.asyn == false)) {
                    _asyn = false;
                }
                var _autoParam = jo.getDefVal(oParams.autoParam, []);//异步加载时自动传到后台的数据
                /*
				 * 异步加载完成后的回调
				 */
                var onAsynSuccess = function (event, treeId, nodeData, msg) {
                    if (typeof (oParams.onAsynSuccess) == "function") {
                        oParams.onAsynSuccess(event, treeId, nodeData, msg);
                    }
                };
                setting = {
                    data: {
                        simpleData: {
                            enable: true,
                            idKey: _itemId,
                            pIdKey: _itemPid
                        },
                        key: {
                            name: _showField,
                            title: _title
                        }
                    },
                    async: {
                        enable: _asyn,
                        dataType: "text",
                        url: jo.getChildUrl,
                        autoParam: _autoParam,
                        dataFilter: jo.dealChildData
                    },
                    callback: {
                        onClick: _onClickFunc,
                        onAsyncSuccess: onAsynSuccess
                    },
                    view: {
                        showLine: false
                    }
                };
                if (jo.isValid(oParams.check)) {
                    setting.check = oParams.check;//勾选配置
                }
                if (jo.isValid(oParams.edit)) {
                    setting.edit = oParams.edit;//编辑配置
                }
            }
            //初始化树前置操作
            if (typeof (oParams.initZTreeBefore) == "function") {
                oParams.initZTreeBefore(_treeId, setting, _zNodes);
            }
            $.fn.zTree.init($("#" + _treeId), setting, _zNodes);
            //创建树对象,jo.zTrees进行统一管理
            //存入之前先删除掉节点,设置,事件等无用信息
            delete (oParams.zNodes);
            delete (oParams.setting);
            delete (oParams.onClickFunc);
            jo.zTrees[_treeId] = oParams;//存入jo.zTrees,key为树的id,调用示例(获取xx树的节点id字段):jo.zTrees["xxTree"].item_id;
        } else {
            console.log("树的参数无效:不是object类型!");
        }
    };
    //[Func] 获取子节点的URL
    jo.getChildUrl = function (treeId, treeNode) {
        return jo.getDefVal(treeNode.childUrl, jo.getCode(jo.zTrees[treeId]["childUrl"], jo.address));
    };
    //[Func] 异步获取到数据后的处理
    jo.dealChildData = function (treeId, parentNode, responseData) {
        return responseData.data;
    };
    //[Func] jo的zTree默认点击事件,回显数据
    jo.zTreeOnClick = function (event, treeId, treeNode) {
        //回显字段默认是节点id字段和显示文本字段,树选择框与父亲属于同一个DOM时有效
        var _fieldId = jo.getDefVal(jo.zTrees[treeId]["field_id"], jo.zTrees[treeId]["item_id"]);
        var _fieldName = jo.getDefVal(jo.zTrees[treeId]["field_name"], jo.zTrees[treeId]["item_show"]);
        var _txtId = jo.zTrees[treeId]["txt_id"];
        if (jo.isValid(_txtId)) {
            jo._("#" + _txtId).val(treeNode[jo.getDefVal(_fieldId, "id")]);
        }
        var _txtName = jo.zTrees[treeId]["txt_name"];
        if (jo.isValid(_txtName)) {
            jo._("#" + _txtName).val(treeNode[jo.getDefVal(_fieldName, "name")]);
        }
    };
    //[Func] 获取zTree树对象
    jo.getZTreeObj = function (treeId) {
        return $.fn.zTree.getZTreeObj(treeId);
    };
    //[Func] 获取zTree树选中的节点对象数组
    jo.getZTreeCheckedNodes = function (treeId) {
        var treeObj = jo.getZTreeObj(treeId);
        return treeObj.getCheckedNodes(true);
    };
    //[Func] 获取zTree树选中的节点id,逗号拼接,默认拼接ID属性,可通过传入id参数指定拼接的属性
    jo.getZTreeCheckedNodesIds = function (treeId, id) {
        var nodes = jo.getZTreeCheckedNodes(treeId);
        //获取勾选中的节点ID
        var navIds = "";
        for (var i = 0; i < nodes.length; i++) {
            navIds += "," + nodes[i][jo.getDefVal(id, "ID")];
        }
        return jo.isValid(navIds) ? navIds.substring(1) : "";
    };
    //zTree--END---
})();
//jo树操作初始化结束

//jo弹出窗口初始化
(function () {
    //[Attr] 存放弹窗对象,key为弹窗的name
    jo.Win = {};

    //jo窗口类
    function JoWin(sUrl, sWidth, sHeight, sTitle, sWinName, sType, dialogElement, isFull, hasShade, oParams) {
        this.name = jo.getDefVal(sWinName, "joWin");//弹窗名字
        this.url = jo.getDefVal(sUrl, "");//请求地址
        this.type = jo.getDefVal(sType, "window");//窗口类型,不同的类型有不同的布局方式,dialog:对话|window:窗口
        this.dialogElement = jo.getDefVal(dialogElement, null);
        if (jo.isValid(this.dialogElement) && typeof (this.dialogElement) == "object") {//当对话节点是真实的页面元素时,窗口大小与元素一致
            this.dialogElement.style.display = "block";
            this.width = jo.getDefVal(this.dialogElement.offsetWidth + 12 + "px", jo.getDefVal(sWidth, "70%"));
            this.height = jo.getDefVal(this.dialogElement.offsetHeight + "px", jo.getDefVal(sHeight, "80%"));
        } else {
            this.width = jo.getDefVal(sWidth, "70%");//宽度
            this.height = jo.getDefVal(sHeight, "80%");//高度
        }
        this.initMargin();//初始化外边距,定位
        this.title = jo.getDefVal(sTitle, "编辑");//标题
        this.isFull = jo.getDefVal(isFull, false);//是否最大化
        this.hasShade = jo.getDefVal(hasShade, true);//是否有遮罩层
        this.params = jo.getDefVal(oParams, {});//其他参数,对象类型
        this.shadeClass = "joWinShade";//遮罩层样式
        if (this.hasShade) {
            this.placeClass = "joWinPlace";//窗口位置,大小样式
        } else {//没有遮罩层时,使用joWinPlace_noShade样式
            this.placeClass = "joWinPlace_noShade";//窗口位置,大小样式
        }
        this.init();//初始化
        jo.Win[this.name] = this;//将窗口对象放入jo.Win,方便调用
    };
    //JoWin原型方法
    JoWin.prototype = {
        initMargin: function () {
            //计算位置,默认居中
            var wW = window.innerWidth;
            var wH = window.innerHeight;
            var _w = wW * 0.7;
            var _h = wH * 0.6;
            if (typeof (this.width) == "number") {
                _w = this.width;
            } else {
                if (this.width.indexOf("%") >= 0) {//百分比表示
                    var t = this.width.replace("%", "").trim();
                    _w = parseInt(t) / 100 * wW;
                } else {
                    _w = jo.getSizeNum(this.width);
                }
            }
            if (typeof (this.height) == "number") {
                _h = this.height;
            } else {
                if (this.height.indexOf("%") >= 0) {//百分比表示
                    var t = this.height.replace("%", "").trim();
                    _h = parseInt(t) / 100 * wH;
                } else {
                    _h = jo.getSizeNum(this.height);
                }
            }
            var left = parseInt((wW - 20 - _w) / 2);//距离左
            var top = parseInt((wH - 2 - _h) / 2) - 10;//距离上
            this.left = left;
            this.top = top;
        },
        //窗口初始化
        init: function () {
            this.scroll = window.document.body.style.overflow;
            window.document.body.style.overflow = "hidden";//去除页面滚动条
            if (this.hasShade) {//存在遮罩层,则对其进行初始化
                this.initShade();
            }
            this.initPlace();//初始化位置和大小
            this.initLayout();//初始化布局
            this.initMain();//初始化主区域
        },
        //初始化遮罩层
        initShade: function () {
            var shadeElement = document.createElement("div");
            //遮罩层ID为窗口名字+Shade
            shadeElement.setAttribute("id", jo.getDefVal(this.name + "Shade", "joShade"));
            //遮罩层样式为joWinShade
            shadeElement.setAttribute("class", this.shadeClass);
            //shadeElement.appendChild(document.createTextNode(""));
            document.body.appendChild(shadeElement);
            this.shadeElement = shadeElement;//将该遮罩层元素存入对象
        },
        //初始化弹窗位置,大小
        initPlace: function () {
            var ePlace = document.createElement("div");
            ePlace.setAttribute("id", jo.getDefVal(this.name + "Place", "joPlace"));
            ePlace.setAttribute("class", this.placeClass);
            if (jo.isValid(this.dialogElement) && typeof (this.dialogElement) && !jo.isValid(this.url)) {
                ePlace.style.width = this.width;
                ePlace.style.height = this.height;

            } else {
                ePlace.style.width = this.width;
                ePlace.style.height = this.height;
                /*ePlace.style.width = this.width;
					ePlace.style.height = this.height;*/
            }
            //alert(this.width+"\n"+this.left+"\n"+ePlace.style.width+"--"+ePlace.style.height);
            if (this.hasShade) {//存在遮罩层,则插入到遮罩层中
                //ePlace.style.marginTop = this.top + "px";
                //ePlace.style.marginLeft = this.left + "px";
                ePlace.style.top = this.top + "px";
                ePlace.style.left = this.left + "px";
                this.shadeElement.appendChild(ePlace);
            } else {
                ePlace.style.top = this.top + "px";
                ePlace.style.left = this.left + "px";
                document.body.appendChild(ePlace);
            }
            //添加事件,用于拖拽
            var mFlag = false;//拖拽开关
            var iTop = 0;//光标距离顶部
            var iLeft = 0;//光标距左
            var objTop = 0;//拖拽对象距上
            var objLeft = 0;//拖拽对象距左
            //鼠标按下事件
            ePlace.onmousedown = function (event) {
                mFlag = true;//打开开关
                iTop = event.clientY;
                iLeft = event.clientX;
                var obj = event.currentTarget;//拖拽对象
                objTop = obj.offsetTop;
                objLeft = obj.offsetLeft;
            };
            //鼠标松开停止拖拽
            ePlace.onmouseup = function (event) {
                mFlag = false;//关闭开关
            };
            //鼠标移动时进行拖拽
            ePlace.onmousemove = function (event) {
                if (mFlag) {//拖拽开关开启时进行拖拽操作,也就是鼠标左键按下不松手进行拖拽
                    var newTop = event.clientY;
                    var newLeft = event.clientX;
                    var obj = event.currentTarget;//拖拽对象

                    //新位置=原位置+变化距离
                    objTop = objTop + (newTop - iTop);
                    objLeft = objLeft + (newLeft - iLeft);

                    //更新参数
                    iTop = newTop;
                    iLeft = newLeft;

                    //修改位置
                    obj.style.top = objTop + "px";
                    obj.style.left = objLeft + "px";


                }
            };
            //鼠标移出元素,关闭拖拽
            ePlace.onmouseout = function (event) {
                mFlag = false;//关闭开关
            };
            this.placeElement = ePlace;
        },
        //初始化弹窗布局
        initLayout: function () {
            if (this.type == "dialog") {
                //对话类型的弹窗,没有布局,place元素下全是内容,直接初始化Main
            } else {
                //关闭按钮
                var btn_close = "<a href='javascript:jo.Win.close(\"" + this.name + "\")' class='joWinClose'></a>";
                //放大缩小按钮
                var btn_chengeSize = "<a href='javascript:jo.Win.changeSize(\"" + this.name + "\")' class='joWinSize'></a>";
                //刷新按钮
                var btn_refresh = "<a href='javascript:jo.Win.refresh(\"" + this.name + "\")' class='joWinRefresh'></a>";
                var layout = "<table class='joWinTb' id='" + this.name + "Tb' cellpadding='0' cellspacing='0'>";
                layout += "<tr>" +
                    "<td class='td_11'></td>" +
                    "<td>" +
                    "<table class='joWinTb_title' cellpadding='0' cellspacing='0'>" +
                    "<tr>" +
                    "<td class='joWinTb_title_font' id='" + this.name + "Title'>" + this.title + "</td>" +
                    "<td class='joWinTb_title_btn'>" + btn_close + btn_chengeSize + btn_refresh + "</td>" +
                    "</tr>" +
                    "</table>" +
                    "</td>" +
                    "<td class='td_13'></td>" +
                    "</tr>";
                layout += "<tr>" +
                    "<td class='td_21'></td>" +
                    "<td style='vertical-align:top;height:100%;'>" +
                    "<div id='" + this.name + "Main' class='joWinMain'>" +
                    "</div>" +
                    "</td>" +
                    "<td class='td_23'></td>" +
                    "</tr>";
                layout += "<tr>" +
                    "<td class='td_31'></td>" +
                    "<td class='td_32'></td>" +
                    "<td class='td_33'></td>" +
                    "</tr>";
                layout += "</table>";
                this.placeElement.innerHTML = layout;
                this.titleElement = document.getElementById(this.name + "Title");
                var mainElement = document.getElementById(this.name + "Main");
                this.mainElement = mainElement;
            }
        },
        //初始化主要内容
        initMain: function () {
            if (jo.isValid(this.url)) {//url有效,则生成iframe元素
                var iframe = document.createElement("iframe");
                iframe.setAttribute("name", this.name + "Iframe");
                iframe.setAttribute("src", this.url);
                iframe.setAttribute("class", "joWinIframe");
                iframe.setAttribute("id", this.name + "Iframe");
                if (this.type == "dialog") {
                    this.placeElement.appendChild(iframe);
                } else {
                    this.mainElement.appendChild(iframe);
                }
                this.iframeElement = iframe;
            } else {//没有url,则生成html元素
                if (typeof (this.dialogElement) == "object") {//插入的是节点
                    if (this.type == "dialog") {
                        this.dialogElement.style.display = "block";//显示
                        this.placeElement.appendChild(this.dialogElement);
                    } else {
                        this.dialogElement.style.display = "block";//显示
                        this.mainElement.appendChild(this.dialogElement);
                    }
                } else {//插入的是html代码
                    if (this.type == "dialog") {
                        this.placeElement.innerHTML = this.dialogElement;
                    } else {
                        this.mainElement.innerHTML = this.dialogElement;
                    }
                }
            }
        },
        //关闭弹窗
        close: function () {
            window.document.body.style.overflow = this.scroll;//恢复页面滚动条
            if (this.hasShade) {//存在遮罩层,直接移除遮罩
                if (jo.isValid(this.dialogElement) && typeof (this.dialogElement) == "object") {
                    //还原元素
                    document.body.appendChild(this.dialogElement);
                }
                jo.remove(this.shadeElement);
            } else {//不存在遮罩,移除定位层
                jo.remove(this.placeElement);
            }
        },
        //最大化,最小化
        changeSize: function () {
            if (this.isFull) {//已经是最大化,变小
                this.placeElement.style.width = this.width;
                this.placeElement.style.height = this.height;
                if (!this.hasShade) {//没有遮罩,添加边距
                    this.placeElement.style.top = this.top + "px";
                    this.placeElement.style.left = this.left + "px";
                } else {
                    //this.placeElement.style.marginLeft = this.left + "px";
                    //this.placeElement.style.marginTop = this.top + "px";
                    this.placeElement.style.top = this.top + "px";
                    this.placeElement.style.left = this.left + "px";
                }
                //布局大小变化
                this.isFull = false;
                this.refresh();
            } else {//不是最大化,变大
                this.placeElement.style.width = "100%";
                this.placeElement.style.height = "100%";
                if (!this.hasShade) {//没有遮罩,去除外边距
                    this.placeElement.style.top = "0px";
                    this.placeElement.style.left = "0px";
                } else {
                    //this.placeElement.style.marginLeft = "0px";
                    //this.placeElement.style.marginTop = "0px";
                    this.placeElement.style.top = "0px";
                    this.placeElement.style.left = "0px";
                }
                this.isFull = true;//改变状态
                this.refresh();
            }
        },
        //刷新
        refresh: function () {
            window[this.name + "Iframe"].location.href = window[this.name + "Iframe"].location.href;
        }
    };
    //[Func] 关闭弹窗
    jo.Win.close = function (sWinName) {
        if (jo.isValid(sWinName)) {
            jo.Win[sWinName].close();
        } else {
            var tName = "";
            for (var n in jo.Win) {
                tName = n;
            }
            //删除最后一个
            if (jo.isValid(tName)) {
                jo.Win[tName].close();
            } else {
                jo.showMsg("获取弹窗name属性失败!");
            }
        }
    };
    //[Func] 弹窗最大/最小化
    jo.Win.changeSize = function (sWinName) {
        jo.Win[sWinName].changeSize();
    };
    //[Func] 刷新弹窗
    jo.Win.refresh = function (sWinName) {
        jo.Win[sWinName].refresh();
    };

    //[Func] 弹出对话窗dialog,插入的是html节点元素对象或者代码,暂时不支持url
    jo.showDialog = function (dialogElement, sWinName, hasShade) {
        if (jo.isValid(dialogElement)) {
            new JoWin('', '', '', '', jo.getDefVal(sWinName, "dialog"), "dialog", dialogElement, '', hasShade, '');
        }
    };
    //[Func] 弹出窗口,插入的是url
    jo.showWin = function (sUrl, sWidth, sHeight, sTitle, sWinName, isFull, hasShade) {
        if (jo.isValid(sUrl)) {
            new JoWin(sUrl, sWidth, sHeight, sTitle, sWinName, 'window', '', isFull, hasShade, '');
        }
    };
    //[Func] 弹出对话窗口,插入的是元素
    jo.showDialogWin = function (dialogElement, sTitle, sWinName, hasShade) {
        if (jo.isValid(dialogElement)) {
            new JoWin('', '', '', sTitle, jo.getDefVal(sWinName, "dialogWin"), "window", dialogElement, '', hasShade, '');
        }
    };


    //[Func] 弹出选择列表窗口
    jo.selectList = function (sUrl, inpId, inpName, fieldId, fieldName, multiple, callback) {
        //[Attr] 选择框参数
        jo.selectParams = {
            "url": sUrl,
            "field_id": jo.getDefVal(fieldId, "ID"),
            "field_text": jo.getDefVal(fieldName, "NAME"),
            "id": jo.getDefVal(inpId, ""),
            "text": jo.getDefVal(inpName, ""),
            "multiple": jo.getDefVal(multiple, false)
        };
        if (typeof (callback) == "function") {
            //[Func] 列表选择的回调函数,
            //参数1[status]表示回调类型,true表示确定按钮的回调,false表示取消的回调
            //参数2[checkedIds]表示选中项ids,多个使用逗号间隔
            //参数3[checkedNames]表示选中项names,多个使用逗号间隔
            jo.selectListCall = callback;
        }
        //弹出窗口
        return jo.showWin(contextPath + "common/choice_list.jsp", "400px", "80%", "选择");
    };

    //[Func] 弹出选择树窗口,参数zTreeParamOrCallBack表示ztee树的参数(jo.drawZTree的参数),或者是初始化树的方法(参数为ajax返回结果)
    jo.selectTreeOld = function (sUrl, inpId, inpName, fieldId, fieldName, fieldPid, multiple, callback, zTreeParamOrCallBack, nodeCanChoiceCallBack, drawBeforeCallBack) {
        //[Attr] 选择框参数
        jo.selectParams = {
            "url": sUrl,
            "field_id": jo.getDefVal(fieldId, "ID"),
            "field_text": jo.getDefVal(fieldName, "NAME"),
            "field_pid": jo.getDefVal(fieldPid, "PARENT_ID"),
            "id": jo.getDefVal(inpId, ""),
            "text": jo.getDefVal(inpName, ""),
            "multiple": jo.getDefVal(multiple, false)
        };
        if (typeof (callback) == "function") {
            //[Func] 列表选择的回调函数,
            //参数[status]表示回调类型,true表示确定按钮的回调,false表示取消的回调
            //参数2[checkedIds]表示选中项ids,多个使用逗号间隔
            //参数3[checkedNames]表示选中项names,多个使用逗号间隔
            jo.selectListCall = callback;
        }

        //生成树回调
        if (typeof (zTreeParamOrCallBack) == "function") {//回调有2个参数,参数1为ajax返回的结果result,参数2为选择框window对象
            jo.selectTreeDrawTree = zTreeParamOrCallBack;
        } else {
            jo.selectParams["zTreeParam"] = zTreeParamOrCallBack;
        }

        //选择树的节点是否可选回调函数,参数为树节点对象
        if (typeof (nodeCanChoiceCallBack) == "function") {
            jo.selectTreeNodeCanChoice = nodeCanChoiceCallBack;
        }

        //生成树之前的处理回调
        if (typeof (drawBeforeCallBack) == "function") {
            jo.selectTreeDrawBefore = drawBeforeCallBack;
        }

        //弹出窗口
        return jo.showWin(contextPath + "common/choice_tree.jsp", "400px", "80%", "选择");
    };

    //[Func] 弹出图标选择窗口,回调参数为所选择的图标class属性
    jo.selectIcon = function (inpId, callback) {
        //弹出图标选择窗口的图标单击事件:参数为图标的class属性
        if (typeof (callback) == "function") {
            jo.iconSelectClick = callback;//自定义回调
        } else {
            //默认回调
            jo.iconSelectClick = function (sClass) {
                $("#" + jo.getDefVal(inpId, "ICON")).val(sClass);
                jo.Win.close();
            }
        }
        //弹出窗口
        return jo.showWin(contextPath + 'page/common/choice_icon.html', '520px', '320px', '选择图标');
    };

    //基于SelectTree类的封装
    //[Func] 树选择插件入口,供使用,当参数1为object时(推荐使用此方式),表示参数以对象形式传入;否则默认是老版本树插件参数模式
    jo.selectTree = function (sUrl, inpId, inpName, fieldId, fieldName, fieldPid, multiple, callback, paramObj) {
        var options = {};
        if (typeof (sUrl) == "object") {
            options = sUrl;
        } else {
            options.url = sUrl;
            options.field_id = fieldId;
            options.field_text = fieldName;
            options.field_pid = fieldPid;
            options.inp_id = inpId;
            options.inp_text = inpName;
            options.multiple = multiple;
            if (typeof (callback) == "function") {
                //[Func] 列表选择的回调函数,
                //参数[status]表示回调类型,true表示确定按钮的回调,false表示取消的回调
                //参数2[checkedIds]表示选中项ids,多个使用逗号间隔
                //参数3[checkedNames]表示选中项names,多个使用逗号间隔
                options.selectListCall = callback;
            }
            if (paramObj) {
                options.id = paramObj.id;//一个页面多个树需要区分id

                //-----------兼容旧版本树选择
                //生成树回调
                if (typeof (paramObj.zTreeParamOrCallBack) == "function") {//回调有1个参数,参数1为ajax返回的结果result
                    jo.selectTreeDrawTree = paramObj.zTreeParamOrCallBack;
                } else {
                    options.zTreeParam = paramObj.zTreeParamOrCallBack;
                }
                //选择树的节点是否可选回调函数,参数为树节点对象
                if (typeof (paramObj.nodeCanChoiceCallBack) == "function") {
                    jo.selectTreeNodeCanChoice = paramObj.nodeCanChoiceCallBack;
                }

                //生成树之前的处理回调,参数1为ajax返回data数组(也就是要树list),参数2为树的id
                if (typeof (paramObj.drawBeforeCallBack) == "function") {
                    jo.selectTreeDrawBefore = paramObj.drawBeforeCallBack;
                }
            }
        }
        jo.getSelectTree(options).show();
    };
    //[Attr] 树选择插件映射,key为SelectTree的id,value为SelectTree
    jo.SelectTrees = {};
    //[Func] 生成树选择插件对象
    jo.getSelectTree = function (options) {
        var id = '';
        if (options && options.id) {
            id = options.id;
        } else {//id无效,则返回默认树
            id = 'default';
        }
        if (!jo.SelectTrees[id]) {//编号对于的树不存在,则创建
            jo.SelectTrees[id] = new SelectTree(options);
        }
        return jo.SelectTrees[id];
    };
})();
//jo弹出窗口初始化结束

//jo 对象型插件初始化
(function () {
    //[Object] 倒计时对象,参数格式:"HH:MM:SS" 时分秒,callBack为回调函数
    function TimeDown(sTime, callBack, overCallBack) {
        var arr = sTime.split(":");
        this.val = sTime;
        this.HH = parseInt(arr[0]);
        this.MM = parseInt(arr[1]);
        this.SS = parseInt(arr[2]);

        function start_(sVal) {
            var HH = parseInt(sVal.split(":")[0]);
            var MM = parseInt(sVal.split(":")[1]);
            var SS = parseInt(sVal.split(":")[2]);
            if (SS > 0) {//秒大于0
                SS -= 1;
            } else {//秒为0
                if (MM > 0) {//分大于0
                    MM -= 1;//分减1
                    SS = 59;//秒调到59
                } else {//分秒都为0
                    if (HH > 0) {//时大于0
                        HH -= 1;//时减1
                        MM = 59;//分调到59
                        SS = 59;//秒也调到59
                    } else {//时分秒都为0,倒计时结束
                        if (typeof (overCallBack) == "function") {
                            overCallBack();
                        } else {
                            alert("时间到!");
                        }
                        return;
                    }
                }
            }
            var res = (HH < 10 ? "0" + HH : HH) + ":" + (MM < 10 ? "0" + MM : MM) + ":" + (SS < 10 ? "0" + SS : SS);
            var val = res;
            if (jo.isValid(callBack) && typeof (callBack) == "function") {
                callBack(res);//回调函数
            }
            setTimeout(function () {
                start_(val)
            }, 1000);
        }

        this.start = function () {
            start_(this.val);
        };
    }

    //[Func] jo暴露出的调用倒计时插件的接口方法,按秒倒计时
    jo.timeDown = function (sTime, callBack, overCallBack) {
        new TimeDown(sTime, callBack, overCallBack).start();
    };
})();
//jo 对象型插件初始化结束

//jo特效处理初始化
(function () {
    //设置拖拽
    jo.setMove = function (element) {
        //默认为绝对定位
        if (jo.getDefVal(element.style.position, "") != "absolute") {
            element.style.position = "absolute";
        }
        var mFlag = false;//拖拽开关
        var iTop = 0;//光标距离顶部
        var iLeft = 0;//光标距左
        var objTop = 0;//拖拽对象距上
        var objLeft = 0;//拖拽对象距左
        //鼠标按下事件
        element.onmousedown = function (event) {
            mFlag = true;//打开开关
            iTop = event.clientY;
            iLeft = event.clientX;
            var obj = event.currentTarget;//拖拽对象
            objTop = obj.offsetTop;
            objLeft = obj.offsetLeft;
        };
        //鼠标松开停止拖拽
        element.onmouseup = function (event) {
            mFlag = false;//关闭开关
        };
        //鼠标移动时进行拖拽
        element.onmousemove = function (event) {
            if (mFlag) {//拖拽开关开启时进行拖拽操作,也就是鼠标左键按下不松手进行拖拽
                var newTop = event.clientY;
                var newLeft = event.clientX;
                var obj = event.currentTarget;//拖拽对象

                //新位置=原位置+变化距离
                objTop = objTop + (newTop - iTop);
                objLeft = objLeft + (newLeft - iLeft);

                //更新参数
                iTop = newTop;
                iLeft = newLeft;

                //修改位置
                obj.style.top = objTop + "px";
                obj.style.left = objLeft + "px";
                //alert(obj.style.top+"\n"+obj.style.left);

            }
        };
        //鼠标移出元素,关闭拖拽
        element.onmouseout = function (event) {
            mFlag = false;//关闭开关
        };
    };

    //[Func] 元素设置箭头控制移动
    jo.setMoveByKey = function (element, bFlag) {
        var kFlag = jo.getDefVal(bFlag, false);//键盘控制移动的开关
        var kTop = kBottom = kLeft = kRight = false;
        var kTimer = null;//轮询监听控制
        var moveObj = element;
        var ix = 10;//每次移动的距离
        var ims = 30;//灵敏度(越小越灵敏).2次移动之间的时间间隔
        //初始化移动事件
        if (kFlag) {
            kTimer = window.setInterval(function () {
                if (kFlag) {
                    if (kTop) {
                        moveObj.style.top = moveObj.offsetTop - ix + "px";
                    } else if (kBottom) {
                        moveObj.style.top = moveObj.offsetTop + ix + "px";
                    } else if (kLeft) {
                        moveObj.style.left = moveObj.offsetLeft - ix + "px";
                    } else if (kRight) {
                        moveObj.style.left = moveObj.offsetLeft + ix + "px";
                    }
                }
            }, ims);
        }
        //鼠标点击该元素,控制移动开关
        element.onclick = function (event) {
            kFlag = !kFlag;
            if (kFlag) {
                moveObj = event.currentTarget;
                kTimer = window.setInterval(function () {
                    if (kFlag) {
                        if (kTop) {
                            moveObj.style.top = moveObj.offsetTop - ix + "px";
                        } else if (kBottom) {
                            moveObj.style.top = moveObj.offsetTop + ix + "px";
                        } else if (kLeft) {
                            moveObj.style.left = moveObj.offsetLeft - ix + "px";
                        } else if (kRight) {
                            moveObj.style.left = moveObj.offsetLeft + ix + "px";
                        }
                    }
                }, ims);
            } else {
                if (kTimer) {
                    clearInterval(kTimer);
                }
            }
        };
        //上下左右键捕捉
        document.onkeydown = function (event) {
            var keyCode = event.keyCode;

            switch (keyCode) {
                case 37:
                    kLeft = true;
                    break;
                case 38:
                    kTop = true;
                    break;
                case 39:
                    kRight = true;
                    break;
                case 40:
                    kBottom = true;
                    break;
            }
        };
        document.onkeyup = function (event) {
            var keyCode = event.keyCode;

            switch (keyCode) {
                case 37:
                    kLeft = false;
                    break;
                case 38:
                    kTop = false;
                    break;
                case 39:
                    kRight = false;
                    break;
                case 40:
                    kBottom = false;
                    break;
            }
        };
    };
})();
//jo特效处理初始化结束

//jo组件相关初始化开始
//下拉列表模型
var JoSelectTool = {};//jo下拉组件池
function JoSelect(obj) {
    //初始化数据
    this.element = obj;
    this.data = new Array();//下拉列表数据
    this.keyField = jo.getDefVal(this.element.getAttribute("keyField"), "ID");//选项key字段
    this.valueField = jo.getDefVal(this.element.getAttribute("valueField"), "NAME");//选项value字段
    this.firstItem = null;
    this.async = false;//异步标识,默认为false(同步)
    this.state = 1;//组件状态,默认为1(新建状态),状态分别是: 1(新建),2(正在初始化),3(正在装载/格式化),4(正常状态)
    var _async = this.element.getAttribute("async");//异步属性
    if (typeof (_async) == 'boolean' && _async == true) {
        this.async = true;
    } else if (typeof (_async) == 'string' && _async == 'true') {
        this.async = true;
    } else if (typeof (_async) == 'string' && _async == '1') {
        this.async = true;
    } else if (typeof (_async) == 'string' && _async == 'yes') {
        this.async = true;
    }
    this.id = jo.getDefVal(this.element.getAttribute("id"), this.element.getAttribute("name"));
    if (this.id) {
        JoSelectTool[this.id] = this;//缓存当前下拉对象
    } else {
        console.warn("JoSelect查找id和name属性失败,请确认该下拉框相关属性有效!此错误可能会导致事件监听异常!");
    }
    this.event = {};//事件
    this.waitValue = null;//等待设置的属性值,设置完成后需要清空
    /**
     * 设置值
     * 如果调用此方法时组件还没有装载完毕,则会等到装载结束后进行赋值
     * 最差的情况是会执行2次赋值操作,但没有任何影响
     * @param val 值
     */
    this.setValue = function (val) {
        //放在这里缓存,而不是放在下面的else代码块,主要是为了解决因为线程安全问题带来值未正确设置问题
        //比如:在判断状态==4失败之后,在缓存waitValue之前,组件装载完毕,状态回归4,最后才执行的设置waitValue,此时会导致值没有得到设置
        //将此操作提前在状态判断之前可以避免上述问题,唯一的不足是可能会发生2次设置值的操作,但是不影响,重复设置相同的值不会像上述问题一样带来不可原谅的错误
        this.waitValue = val;
        if (this.state == 4) {//正常状态,则直接赋值
            //注释掉该行主要还是为了那一丢丢的出错可能性,注释掉后就算会赋值2次起码不会有赋值null的情况
            //this.waitValue = null;//保证在设置值之后该属性为null,多线程下有可能会导致设置2次值,而那一次可能会设置null
            this.element.value = val;
        }
    };
    /**
     * 清除,将元素之设为空,下拉选项归为默认项
     */
    this.clear = function () {
        this.data = null;
        this.element.setAttribute("data", '');
        if (jo.isValid(this.firstItem)) {
            var _html = '<option value="' + this.firstItem[this.keyField] + '">' + this.firstItem[this.valueField] + '</option>';
            this.element.innerHTML = _html;
        }
        this.element.value = '';
    };
    /**
     * 注册事件,支持同一事件个回调
     * 要注意注册顺序,只有在回调时间点前注册才有效
     * @param eventName 事件名称,loaded:加载完成事件
     * @param callback 事件对应的回调函数
     */
    this.on = function (eventName, callback) {
        if (this.event[eventName]) {//存在此事件
            this.event[eventName].push(callback);
        } else {
            this.event[eventName] = [callback];
        }
    };
    /**
     * 初始化
     */
    this.init = function () {
        this.state = 2;//初始化状态,因为本方法总是会调用load()方法,所以不需要考虑方法结束时状态是什么
        this.showLoading("正在加载...");
        if (jo.isValid(this.element.getAttribute("firstItem"))) {
            this.firstItem = JSON.parse(this.element.getAttribute("firstItem"));
        }
        //存在请求地址,则获取数据
        if (jo.isValid(this.element.getAttribute("dataUrl"))) {
            //请求该地址的数据
            var _this = this;//定义_this指向当前对象,处理回调等函数内部不识别this
            //定义获取列表数据后的执行函数
            var _temp = function () {
                var sData = jo.getDefVal(_this.element.getAttribute("data"), "");
                if (jo.isValid(sData)) {
                    if (typeof (sData) == "string") {
                        _this.data = eval(sData);
                    } else {
                        _this.data = sData;
                    }
                } else {
                    console.warn("下拉列表组件数据来源信息为空!");
                }
                _this.load();//装载 数据
            };
            var sUrl = this.element.getAttribute("dataUrl");//数据地址
            sUrl = jo.parseUrl(sUrl);
            /*$.getJSON(sUrl, function(result){
				_this.element.setAttribute("data",JSON.stringify(result.data));
				_temp();
			});*/
            jo.postAjax(sUrl, {}, function (result) {
                _this.element.setAttribute("data", JSON.stringify(result.data));
                _temp();
            }, this.async);
        } else {
            var sData = jo.getDefVal(this.element.getAttribute("data"), "");
            if (jo.isValid(sData)) {
                if (typeof (sData) == "string") {
                    this.data = eval(sData);
                } else {
                    this.data = sData;
                }
            } else {
                console.log("下拉列表组件数据来源信息为空!");
            }
            this.load();//装载 数据
        }
    };
    //加载,参数为数组类型
    this.load = function (oData) {
        this.state = 3;//装载状态
        if (oData) {
            this.data = oData;
            this.element.setAttribute("data", JSON.stringify(oData));
        }
        if (this.data && this.data.length) {
            var _html = '';
            var k = this.keyField;
            var v = this.valueField;
            if (jo.isValid(this.firstItem)) {
                _html += '<option value="' + this.firstItem[k] + '">' + this.firstItem[v] + '</option>';
            }
            for (var i = 0; i < this.data.length; i++) {
                var item = this.data[i];
                _html += '<option value="' + item[k] + '">' + jo.parseKey(item, v) + '</option>';
            }
            this.element.innerHTML = _html;
        } else {
            this.element.setAttribute("data", '');
            this.element.innerHTML = '<option value=""></option>';//默认为空,如果什么都没有,在表单取值时会有问题
        }

        if (this.waitValue != null) {//等待设置的值不为null,则进行赋值
            this.element.value = this.waitValue;
            this.waitValue = null;//设置完后,继续回归null
        }
        this.state = 4;//正常使用状态
        console.info("JoSelect[" + this.id + "]装载完成!");
        //joSelect装载完成事件
        if (this.event["loaded"]) {
            for (var i = 0; i < this.event["loaded"].length; i++) {
                this.event["loaded"][i](this);//回调
            }
        }
    };
    //显示正在加载
    this.showLoading = function (str) {
        if (str == false) {

        } else {
            this.element.innerHTML = '<option value="">' + jo.getDefVal(str, "正在加载...") + '</option>';
        }
    };
}

/**
 * Grid中的Col对象,记录Grid中每一列的相关属性
 * @param jqColElement col标签对应的jq对象
 * @returns
 */
function Col(jqColElement) {
    this.obj = jqColElement;
    this.field = jo.getDefVal(jqColElement.attr("field"), "");
    this.title = jo.getDefVal(jqColElement.attr("title"), "");
    this.width = jo.getDefVal(jqColElement.attr("width"), "");
    this.align = jo.getDefVal(jqColElement.attr("align"), "left");//表格内容默认居左
    this.event = jo.getDefVal(jqColElement.attr("event"), "none");
    this.headAlign = jo.getDefVal(jqColElement.attr("headAlign"), this.align);//标题水平位置,默认居左
    this.order = jo.getDefVal(jqColElement.attr("order"), "");//列排序标记
    this.sort = jo.getDefVal(jqColElement.attr("sort"), this.order ? true : false);//layui中使用sort做排序标记
    this.type = jo.getDefVal(jqColElement.attr("type"), "normal");//列类型,默认为normal,多选框:checkbox,单选框:radio
    this.tips = jo.getDefVal(jqColElement.attr("tips"), "");//鼠标悬浮时的提示文案,通常用来显示超出长度的文案
    this.fixed = jo.getDefVal(jqColElement.attr("fixed"), "");//表头固定
    //col对象转json,供joView使用
    this.col2Json = function () {
        var col = {};
        col["field"] = this.field;
        col["title"] = this.title;
        col["width"] = this.width;
        col["align"] = this.align;
        col["event"] = this.event;
        col["headAlign"] = this.headAlign;
        col["order"] = this.order;
        col["sort"] = this.sort;
        col["type"] = this.type;
        col["tips"] = this.tips;
        col["fixed"] = this.fixed;
        return col;
    };
}

/**
 * Grid对象,记录Grid表格相关属性,传入的不是字符串时要使用new关键字
 * @param jqGridElement Grid表格对应的jq对象
 * @returns
 */
var GridTool = {};

function Grid(jqGridElement) {
    if (typeof (jqGridElement) == "string") {//当传入表格id时
        if (GridTool[jqGridElement]) {//如果已存在,返回引用
            return GridTool[jqGridElement];
        } else {
            //必须使用new关键字,原先不使用new关键字的方式会导致this指向的是window对象(不是我们想要的Grid对象)
            return new Grid($("#" + jqGridElement));
            //jqGridElement = $("#"+jqGridElement);
        }
    }
    this.obj = $(jqGridElement);//记录该Grid对象(jq对象)
    this.dataUrl = jo.getDefVal(this.obj.attr("dataUrl"), "");//数据源url
    this.deleteUrl = jo.getDefVal(this.obj.attr("deleteUrl"), "");//删除数据的url
    this.formUrl = jo.getDefVal(this.obj.attr("formUrl"), "");//打开表单对应的url
    this.cols = new Array();//Col对象数组
    this.data = null;//表格数据
    this.titleInited = false;//标记标题初始化状态
    this.titleCss = jo.getDefVal(this.obj.attr("titleCss"), "");//标题行样式
    this.trClick = jo.getDefVal(this.obj.attr("trClick"), "");//行点击事件方法名,推荐不带括号
    this.noHead = jo.getDefVal(this.obj.attr("noHead"), "false");//不要标题行
    this.trHandle = jo.getDefVal(this.obj.attr("trHandle"), "");//行处理函数,生成表格行之前的行对象处理,参数1为行数据对象,参数2为在数据中的位置索引
    if (typeof this == "object") {
        this.id = this.obj.attr("id");
        GridTool[this.id] = this;//将此Grid引用存入池中,方便下次获取
    }
    //兼容layui相关参数
    this.layTable = null;//laytable对象
    this.height = jo.getDefVal(this.obj.attr("lay-height"), "");//高度
    this.page = jo.getDefVal(this.obj.attr("page"), "");//是否开启分页
    if (this.page == "true") {
        this.page = true;
    } else {
        this.page = false;
    }
    //初始化
    var _cols = this.obj.find("col");
    if (_cols && _cols.length > 0) {//存在col配置
        for (var i = 0; i < _cols.length; i++) {
            var col = new Col($(_cols[i]));
            this.cols.push(col);
            jo.remove(_cols[i]);//记录结束col数据后删除,原因: 不删除会影响宽度属性异常
        }
    }
    //添加thead和tbody标签,用于存放标题行和内容
    this.obj.html("<thead></thead><tbody></tbody>");

    //返回该Grid对象的colsInfo信息,供joView使用
    this.colsInfo = function () {
        var _colsInfo = new Array();
        for (var i = 0; i < this.cols.length; i++) {
            _colsInfo.push(this.cols[i].col2Json());
        }
        return _colsInfo;
    };
    //初始化标题行
    this.initTitle = function () {
        //存在属性noHead=true时不生成标题行
        if (this.noHead == "true" || this.noHead == true || this.noHead == "1" || this.noHead == "yes") {
            return;
        }
        if (jo.isValid(this.cols)) {
            var _headHtml = "<tr>";
            if (jo.isValid(this.titleCss)) {
                _headHtml = '<tr class="' + this.titleCss + '">';
            }
            //遍历生产表头
            for (var i = 0; i < this.cols.length; i++) {
                var col = this.cols[i];
                _headHtml += '<th style="width:' + col.width + ';text-align:' + col.headAlign + ';">';
                if (col.type == 'checkbox') {
                    _headHtml += '<input type="checkbox" onclick="jo.checkAll(\'' + col.field + '\',this)">';
                } else if (col.type == 'radio') {
                    _headHtml += jo.getDefVal(col.title, '选择');
                } else {
                    _headHtml += col.title;
                    if (col.order) {//当前列开启排序功能
                        _headHtml += ' <span style="display: inline-block;width: 16px;height: 15px;position: relative;">';/*caret*/
                        _headHtml += '<div class="order-asc cursor-hand fa fa-angle-up" field="' + col.order + '" grid="' + this.id + '" onclick="GridOrder(this, true)" style="font-weight: bold;height:7px;width: 15px;padding: 0px;margin:0px;position: absolute;top:0px;left:0px;"><!--<i class="fa fa-caret-up" aria-hidden="true"></i>--></div>';
                        _headHtml += '<div class="order-desc cursor-hand fa fa-angle-down" field="' + col.order + '" grid="' + this.id + '" onclick="GridOrder(this, false)" style="font-weight: bold;height:7px;width: 15px;padding: 0px;margin:0px;position: absolute;bottom:0px;left:0px;"><!--<i class="fa fa-caret-down" aria-hidden="true"></i>--></div>';
                        _headHtml += '</span>';
                    }
                }
                _headHtml += '</th>';
            }
            _headHtml += "</tr>";
            this.obj.find(">thead").html(_headHtml);
            this.titleInited = true;
        }
    };
    //清空数据
    this.clearData = function () {
        this.obj.find(">tbody").html("");
        this.data = null;
    };
    //(私有方法)加载json数组数据,加载数据核心实现
    this.loadJsonArray = function (aJson) {
        this.clearData();//清空数据
        if (jo.isValid(this.cols)) {
            for (var m = 0; m < aJson.length; m++) {
                var item = aJson[m];//一条json
                //行数据处理
                if (jo.isValid(this.trHandle)) {
                    if (this.trHandle.lastIndexOf("()") > -1) {
                        //去除()
                        this.trHandle = this.trHandle.replace("()", "");
                    }
                    try {
                        //eval的方式经过jo.obj2JsonStr(item)后与item不是同一个对象,导致钩子函数无法对item做处理
                        //eval(this.trHandle + "(" + jo.obj2JsonStr(item) + ", " + m + ")");
                        if (typeof (window[this.trHandle]) == "function") {
                            window[this.trHandle](item, m);
                        }
                    } catch (err) {
                        console.error("行处理方法" + this.trHandle + "异常!");
                        console.info(err);
                    }
                }
                var _html = '';
                var trCss = jo.getDefVal(item['_css'], '');//行样式
                var trStyle = jo.getDefVal(item['_style'], '');//行style
                if (jo.isValid(this.trClick)) {
                    if (this.trClick.lastIndexOf("()") > -1) {
                        //去除()
                        this.trClick = this.trClick.replace("()", "");
                    }
                    _html = '<tr class="' + trCss + '" style="cursor:pointer;' + trStyle + '" onclick=\'' + this.trClick + '(' + jo.obj2JsonStr(item) + ',' + jo.obj2JsonStr(aJson) + ',' + m + ')\'>';
                } else {
                    _html = '<tr class="' + trCss + '" style="' + trStyle + '">';
                }

                for (var i = 0; i < this.cols.length; i++) {
                    var col = this.cols[i];
                    //var _val = col.field;//col对象配置的field属性
                    //对可执行字段进行处理
                    /*if(jo.isValid(_val)){
					 //以 [= 开头, ] 结尾的格式,表示调用函数对字段进行处理,其中的变量(即该行json的字段值)要使用花括号括起来,例如 [=jo.formatDate('{time}')],time为从数据库查询出来的字段
					 if(_val && /^\[=(.)*\]$/.test(_val)){
					 //去掉无效的[=和]
					 _val = _val.substring(2, _val.length - 1);
					 //解析{xx}类型的变量
					 _val = jo.getCode(_val, item);
					 //执行该函数
					 _val = eval(_val);
					 }else if(/\{(.)*\}/g.test(_val)){//解析{xx}类型的变量
					 _val = jo.getCode(_val, item);
					 }else if(_val && _val == "_seq"){//序号常量,自动生成序号
					 _val = m + 1;
					 }else{
					 _val = jo.getDefVal(item[_val],"");//正常字段名则从json中取对应字段值
					 }
					 }
					 _val = jo.getDefVal(_val,"");//最后对_val进行检查,无效则赋值空格*/
                    var _val = jo.parseExpression(col.field, item, m);//解析表达式
                    var tips = jo.parseExpression(col.tips, item, m);//解析表达式

                    _html += '<td style="text-align:' + col.align + ';" title="' + tips + '">';
                    if (col.type == 'checkbox') {
                        _html += '<input type="checkbox" name="' + col.field + '" value="' + _val + '"/>';
                    } else if (col.type == 'radio') {
                        _html += '<input type="radio" name="' + col.field + '" value="' + _val + '"/>';
                    } else {
                        _html += _val;
                    }
                    _html += '</td>';
                }
                _html += "</tr>";
                this.obj.find(">tbody").append(_html);
            }
            this.data = aJson;

            if (!jo.isValid(this.data)) {
                this.obj.find(">tbody").append("<tr><td colspan='" + this.cols.length + "' style='color:#808080;'>暂无数据！</td></tr>");
            }
        }
    };
    //加载数据
    this.loadData = function (dataOrUrl) {
        if (!this.titleInited) {
            this.initTitle();//标题未初始化则先初始化标题
        }
        var _id = 'loading_' + jo.getUUID();
        this.obj.find(">tbody").html('<tr id="' + _id + '"><td colspan="' + this.cols.length + '">正在加载...</td></tr>');
        if (typeof (dataOrUrl) == "object" && dataOrUrl instanceof Array) {//传入的数组
            this.loadJsonArray(dataOrUrl);
            $("#" + _id).remove();
        } else {//通过url加载
            var surl = jo.getDefVal(dataOrUrl, this.dataUrl);
            if (jo.isValid(surl)) {
                var _this = this;//将this引用指向变量,因为这个this的作用域不在ajax回调当中
                jo.postAjax(surl, {}, function (json) {
                    if (json && json.code == "0") {
                        _this.loadJsonArray(json.data);
                    } else if (json && json.code == "-1") {
                        jo.showMsg(json.info);
                    }
                    $("#" + _id).remove();
                }, true);
            } else {
                $("#" + _id).remove();
            }
        }

    };
}

/**
 * Grid表格排序事件
 * @param element 被点击元素this
 * @param isAsc 是否升序
 * @constructor
 */
function GridOrder(element, isAsc) {
    var grid = GridTool[$(element).attr("grid")];//待排序表格
    var field = $(element).attr("field");//待排序字段
    var p = $(element).parent();//排序容器
    var arr = p.find("div");//0为升序,1为降序
    //将表格下所有的排序图标重置
    grid.obj.find(".order-asc").removeClass("fa-caret-up");
    grid.obj.find(".order-desc").removeClass("fa-caret-down");
    grid.obj.find(".order-asc").addClass("fa-angle-up");
    grid.obj.find(".order-desc").addClass("fa-angle-down");
    if (arr) {
        if (isAsc) {
            $(arr[0]).removeClass("fa-angle-up");
            $(arr[0]).addClass("fa-caret-up");//变成实心图标
            //$(arr[1]).removeClass("fa-caret-down");
            //$(arr[1]).addClass("fa-angle-down");
            //对数据排序
            if (grid) {
                jo.sort(grid.data, function (a, b) {
                    return a[field] < b[field];
                });
                grid.loadData(grid.data);
            }
        } else {
            $(arr[1]).removeClass("fa-angle-down");
            $(arr[1]).addClass("fa-caret-down");//变成实心图标
            //$(arr[0]).removeClass("fa-caret-up");
            //$(arr[0]).addClass("fa-angle-up");
            //对数据排序
            if (grid) {
                jo.sort(grid.data, function (a, b) {
                    return a[field] > b[field];
                });
                grid.loadData(grid.data);
            }
        }
    }
}

/**
 * 树选择插件类
 * @param options
 * @constructor
 */
function SelectTree(options) {
    options = jo.getDefVal(options, {});
    this.id = jo.getDefVal(options.id, 'default');//当前实例唯一编号,默认为default,一个页面只有一个树选择插件时使用默认不会有问题
    this.url = jo.getDefVal(options.url, '');
    this.field_id = jo.getDefVal(options.field_id, 'id');//id字段名
    this.field_text = jo.getDefVal(options.field_text, 'name');//文本字段名
    this.field_pid = jo.getDefVal(options.field_pid, 'parentId');//父节点字段名
    this.inp_id = jo.getDefVal(options.inp_id, 'parentId');//存放id的输入框id
    this.inp_text = jo.getDefVal(options.inp_text, 'parentName');//存放文本的输入框id
    this.multiple = options.multiple == false ? false : true;//默认可以多选
    this.zTreeParam = null;//zTree树参数
    this.left = [];//左侧列表
    this.right = {};//右侧列表
    this.data = [];
    this.selectListCall = options.selectListCall;//回调函数
    this.drawBeforeCallBack = options.drawBeforeCallBack;//画树前回调
    this.nodeCanChoiceCallBack = options.nodeCanChoiceCallBack;//节点是否可选回调,参数为当前点击节点对象treeNode
    var _this = this;//在回调中使用_this来表示树插件对象
    this.htmlCode = '<div id="selectTree_' + this.id + '" class="container-fluid" style="height:100%;padding-bottom: 0px;">'
        + '<div class="row" style="height: 100%;">'
        + '<div class="col-xs-5" style="height:100%;">'
        + '<div id="allChoiceList_' + this.id + '" style="width:100%;height:100%; border:#CCCCCC solid 1px;overflow-x:auto;overflow-y:auto;">'
        + '<ul style="list-style:none;margin:0px;padding:0px;" class="ztree" id="selectZTree_' + this.id + '">'
        + COMMON_CONST.HTML_TREE_LOADING
        + '</ul>'
        + '</div>'
        + '</div>'
        + '<div class="col-xs-2" style="height:100%;">'
        + '<div style="width:100%;height:100%;overflow:hidden;">'
        + '<table style="width:100%; height:100%;text-align:center;margin:0px;padding:0px;" cellpadding="0" cellspacing="0" border="0">'
        + '<tr>'
        + '<td>'
        + '<button id="btn_to_right_' + this.id + '" type="button" class="btn btn-primary" onclick="jo.SelectTrees[\'' + this.id + '\'].checkAll()">'//todo
        + '<i class="fa fa-angle-double-right" aria-hidden="true"></i>'
        + '</button>'
        + '<div style="height:10px;"></div>'
        + '<button id="btn_to_left_' + this.id + '" type="button" class="btn btn-warning" onclick="jo.SelectTrees[\'' + this.id + '\'].backAll()">'
        + '<i class="fa fa-angle-double-left" aria-hidden="true"></i>'
        + '</button>'
        + '</td>'
        + '</tr>'
        + '</table>'
        + '</div>'
        + '</div>'
        + '<div class="col-xs-5" style="height:100%;">'
        + '<div id="choiceList_' + this.id + '" style="width:100%;height:100%; border:#CCCCCC solid 1px;overflow-x:auto;overflow-y:auto;">'
        + '<ul style="list-style:none;margin:0px;padding:0px;" class="choice-item">'//已选
        + '</ul>'
        + '</div>'
        + '</div>'
        + '</div>'
        + '</div>';
    this.show = function () {
        var _id = this.id;
        this.winIdx = jo.showDialog(this.html(), 'selectTreeWin_' + this.id, true, {
            title: '选择框', area: ['450px', '450px'], btn: ["确定", "取消"], btnAlign: 'c', btn1: function () {
                jo.SelectTrees[_id].yes();
            }
        });
        this.init();
    };
    //返回html代码
    this.html = function () {
        return this.htmlCode;
    };
    //将节点添加到左侧待选区,因为ztree树支持children方式设置子节点,所以使用递归方式来将某节点以及子节点全部添加到待选区
    this.addLeftAll = function (node) {
        if (node) {
            this.addLeft(node);//加入左侧栏
            if (node.children && node.children.length > 0) {
                for (var i = 0; i < node.children.length; i++) {
                    this.addLeftAll(node.children[i]);//递归
                }
            }
        }
    };
    //初始化
    this.init = function () {
        //先清空数据
        this.left = [];//左侧列表
        this.right = {};//右侧列表
        this.data = [];
        //初始化按钮
        if (this.multiple) {//多选

        } else {//单选,全选按钮失效
            $("#btn_to_right_" + this.id).attr("disabled", true);
            $("#btn_to_right_" + this.id).css("cursor", "default");
        }
        //查询数据,初始化左侧待选项
        var sUrl = this.url;
        if (jo.isValid(sUrl)) {
            jo.postAjax(sUrl, {}, function (json) {
                if (json && json.code == "0") {
                    var oList = json.data;
                    //画树前回调
                    if (typeof (_this.drawBeforeCallBack) == "function") {
                        _this.drawBeforeCallBack(oList, _this.id);
                    }
                    //生成左侧待选项
                    for (var i = 0; i < oList.length; i++) {
                        var info = oList[i];
                        //TODO 对于树节点是否可点击可以在这里作限制
                        _this.addLeftAll(info);
                    }
                    if (typeof (jo.selectTreeDrawTree) == "function") {
                        _this.drawTree = jo.selectTreeDrawTree;//画树回调
                        _this.drawTree(json);
                    } else if (jo.isValid(_this.zTreeParam)) {
                        _this.zTreeParam["zNodes"] = oList;
                        jo.drawZTree(_this.zTreeParam);
                    } else {
                        //生成树前的处理,老版本树(jsp年代),已作废
                        /*if(typeof (jo.selectTreeDrawBefore) == "function"){
							jo.selectTreeDrawBefore(oList, this.id);
						}*/
                        var param = {
                            treeId: 'selectZTree_' + _this.id
                            , zNodes: oList
                            , item_id: _this.field_id
                            , item_pid: _this.field_pid
                            , item_show: _this.field_text
                            , onClickFunc: _this.leftClick
                        };
                        jo.drawZTree(param);
                    }
                    //初始化右侧已选项
                    try {
                        var ids = $("#" + _this.inp_id).val();
                        if (jo.isValid(ids)) {
                            var idArr = ids.split(",");
                            for (var i = 0; i < idArr.length; i++) {
                                var id = idArr[i];
                                var idx = _this.searchItemIdx(id);
                                if (idx != -1) {//存在该项
                                    var item = _this.left[idx];
                                    _this.addRight(idx, item);
                                }
                            }
                        }
                    } catch (err) {
                        jo.showMsg("已选项初始化异常!");
                        console.error(err);
                    }
                } else {
                    jo.showMsg("获取列表失败!");
                }

            }, true);

        }

    };

    //在待选项中搜索某字段为某值的数据项,返回数据的索引
    this.searchItemIdx = function (val, field) {
        field = jo.getDefVal(field, this.field_id);//默认搜索id字段
        for (var i = 0; i < this.left.length; i++) {
            var item = this.left[i];
            if (item[field] == val) {
                return i;
            } else {
                continue;
            }
        }
        return -1;
    };

    //搜索事件
    this.lastVal = "";//记录上次搜索事件的值
    this.result = {};//搜索结果
    this.searchModal = false;//搜索模式
    this.search = function () {
        var val = $("#search_inp_" + this.id).val();
        if (jo.isValid(val) && val != this.lastVal) {//有效的搜索,防止发生重复搜索
            this.lastVal = val;//记录,防止重复
            var field = this.field_text;//默认搜索显示字段的值
            for (var i = 0; i < this.left.length; i++) {
                var item = this.left[i];
                var tVal = item[field];
                if (tVal.indexOf(val) != -1) {//模糊匹配
                    this.result[i] = item;//满足条件的结果存入result
                } else {
                    continue;
                }
            }
            //显示查询结果
            $("#allChoiceList_" + this.id + " ul").html("");
            for (var m in this.result) {
                var t = this.result[m];
                $("#allChoiceList_" + this.id + " ul").append('<li index="' + m + '" onclick="jo.SelectTrees[\'' + this.id + '\'].leftClick(this)">' + t[this.field_text] + '</li>');//TODO 点击事件有问题
            }
            //记录当前左侧列表显示模式,是否搜索模式
            this.searchModal = true;
            this.result = {};//清空结果表
        } else if (!jo.isValid(val) && this.searchModal) {//当搜索条件为空时,且是搜索模式,则重新生成左侧列表,并切换模式
            //生成左侧待选项
            $("#allChoiceList_" + this.id + " ul").html("");
            for (var i = 0; i < this.left.length; i++) {
                var t = this.left[i];
                $("#allChoiceList_" + this.id + " ul").append('<li index="' + i + '" onclick="jo.SelectTrees[\'' + this.id + '\'].leftClick(this)">' + t[this.field_text] + '</li>');
            }
            this.searchModal = false;
        } else {
            //alert("xxxx"+"--"+val);
        }
    };

    //左侧添加选项
    this.addLeft = function (oItem) {
        var ids = this.left.push(oItem);
        oItem["_ids_"] = ids - 1;//给节点添加索引,方便右侧已选项处理
        //$("#allChoiceList ul").append("<li index='"+(ids-1)+"' onclick='joList.leftClick(this)'>"+oItem[joList.params["field_text"]]+"</li>");
    };
    //左侧列表点击,树节点点击事件
    this.leftClick = function (event, treeId, treeNode) {
        if (_this.multiple || _this.rightNum() == 0) {//多选或当前没有已选项,执行
            _this.addRight(treeNode["_ids_"], treeNode);
        } else if (!_this.multiple && _this.rightNum() == 1) {//单选且已有选项
            _this.backAll();//撤回已选项
            _this.addRight(treeNode["_ids_"], treeNode);//选中当前项
        }
    };
    //判断节点是否可选,默认可选
    this.nodeCanChoice = function (treeNode) {
        /*if(typeof (jo.selectTreeNodeCanChoice) == "function"){
			if(jo.selectTreeNodeCanChoice(treeNode)){
				return true;
			}else{
				return false;
			}
		}*/
        if (typeof (this.nodeCanChoiceCallBack) == "function") {
            if (this.nodeCanChoiceCallBack(treeNode)) {
                return true;
            } else {
                return false;
            }
        }
        return true;
    };

    //获取右侧选项个数
    this.rightNum = function () {
        var i = 0;
        for (var k in this.right) {
            i++;
        }
        return i;
    };
    //右添加选项
    this.addRight = function (ids, oItem) {
        if (this.nodeCanChoice(oItem)) {
            if (jo.isValid(this.right[ids])) {//存在该数据,不做处理

            } else {//不存在该条数据,存入right
                this.right[ids] = oItem;
                $("#choiceList_" + this.id + " ul").append('<li index="' + ids + '" onclick="jo.SelectTrees[\'' + this.id + '\'].rightClick(this)">' + oItem[this.field_text] + '</li>');//todo
            }
        }
    };
    //右侧列表点击
    this.rightClick = function (obj) {
        var ids = $(obj).attr("index");
        //页面去掉该元素,同时right中去掉该条数据
        _this.delRight(obj);
        delete _this.right[ids];//在right对象中删除该数据
    };

    //右侧删除选项
    this.delRight = function (e) {
        jo.remove(e);
    };
    //获取选中的数据某个字段的拼接,逗号链接
    this.getSelectedVal = function (field) {
        var h = "";
        for (var key in this.right) {
            var item = this.right[key];
            h += "," + item[field];
        }
        return h.substring(1);
    };
    //全选
    this.checkAll = function () {
        //循环放入右边
        for (var i = 0; i < this.left.length; i++) {
            this.addRight(i, this.left[i]);
        }
    };
    //全撤回
    this.backAll = function () {
        //清空右侧元素,清空joList.right对象
        $("#choiceList_" + this.id + " ul").html("");
        this.right = {};
    };

    //点击确定
    this.yes = function () {
        if (typeof (this.selectListCall) == "function") {
            this.selectListCall(true, this.getSelectedVal(this.field_id), this.getSelectedVal(this.field_text));
        } else {//默认赋值给对应id的输入框
            if (this.inp_id) {
                var ids = this.getSelectedVal(this.field_id);
                $("#" + this.inp_id).val(ids);
            }
            if (this.inp_text) {
                var texts = this.getSelectedVal(this.field_text);
                $("#" + this.inp_text).val(texts);
            }
            jo.close(this.winIdx);//关闭
        }
    };
    //点击取消
    this.no = function () {
        if (typeof (this.selectListCall) == "function") {
            this.selectListCall(false, this.getSelectedVal(this.field_id), this.getSelectedVal(this.field_text));
        } else {
            jo.close(this.winIdx);
        }
    };
}

//输入选择框,提供联想提示功能
function JoInput(optionOrId) {
    //参数校验
    var params = {};
    if (typeof (optionOrId) == 'object') {
        params = optionOrId;
    } else {
        params.id = optionOrId;
    }
    if (!params.id) {
        console.error('输入选择框id参数不允许为空');
        return null;
    }
    //返回组件
    if (!window['JoInputs']) {
        window['JoInputs'] = {};
    }
    if (window['JoInputs'][params.id]) {
        return window['JoInputs'][params.id];
    }
    var inp = new JoInputClass();
    inp.init(params);
    window['JoInputs'][params.id] = inp;
    return inp;
}

/**
 * 基本的输入选择框(带提示功能的输入框)
 * 对JoInput对象的包装
 * @param id 输入框id
 * @param data 提示数据,通常为字符串,多个使用逗号拼接;也可以是对象数组(用法参考标准JoInput),推荐使用字符串形式
 * @param textField 默认为name(当data为对象数组时需要指定哪个字段是提示信息)
 * @param matchFieldArr 默认为name(当data为对象数组时需要指定使用哪个字段来匹配输入内容)
 * @returns {*} 返回标准的JoInput对象
 * @constructor
 */
function JoInputSimple(id, data, textField, matchFieldArr) {
    if (typeof (data) == 'string') {
        textField = 'name';
        matchFieldArr = ['name'];
        if (data) {
            var arr = data.split(',');
            data = [];
            for (var i = 0; i < arr.length; i++) {
                data.push({name: arr[i], id: arr[i]});
            }
        } else {
            data = [];
        }
    }
    return JoInput({
        id: id,
        data: data,
        idInp: '',
        textInp: id,
        idField: 'id',
        textField: textField,
        editable: true,
        matchField: matchFieldArr,
        matchFailTips: ''
    });
}

//输入选择框组件实际类
function JoInputClass() {

}

JoInputClass.prototype = {
    id: '',
    idInp: '',//存放id值的输入框id
    textInp: '',//存放文本值的输入框id
    idField: '',//id值对应的字段名
    textField: '',//text值对应的字段名
    tipsId: '',//提示窗id
    ele: null,//输入框jq对象
    url: '',
    data: [],//数据
    optionData: [],//当前待选项数据(data的子集)
    matchField: [],//联想提示数据过滤使用的字段
    matchFailTips: '',//关键字匹配为空的提示信息
    key: "",//当前检索的关键字
    lastKey: "",//上一次检索的关键字(联想框关闭后置null),仅仅在入口处用来判断是否和key一致
    searchFlag: false,//检索开关,避免重复执行
    objMap: {},//对象映射
    checkedItem: null,//当前被选中的选项
    checkedKey: null,//当前被选中的选项对应的搜索key
    doClearWhenClose: false,//关闭检索时是否执行清除
    editable: true,//是否可编辑,默认可以在输入框输入,如果为false,则只允许选中下拉列表中的选项
    choiceCallback: null,//选项选择回调函数,参数1为选中的对象,参数2为joinput对象
    clearCallback: null,//清空内容回调函数,参数1为joinput对象
    init: function (params) {//inputId, aList, choiceCallback, clearCallback
        params = params || {};
        this.id = params.id;
        this.idInp = params.idInp;
        this.textInp = params.textInp || this.id;
        this.idField = params.idField || 'id';
        this.textField = params.textField || 'name';
        this.tipsId = 'JoInput_' + this.id;
        this.ele = $("#" + this.id).length > 0 ? $("#" + this.id) : $(".joInput[name='" + this.id + "']");//输入框jq对象
        this.url = '';
        this.data = params.data;
        this.optionData = this.data;
        this.matchFailTips = typeof (params.matchFailTips) != 'undefined' ? params.matchFailTips : '暂无匹配结果~';
        this.matchField = params.matchField ? params.matchField : (this.ele.attr('matchField') ? this.ele.attr('matchField').split(',') : [this.idField, this.textField]);
        this.key = "";//当前检索的关键字
        this.lastKey = "";//上一次检索的关键字
        this.searchFlag = false;//检索开关,避免重复执行
        this.editable = params.editable == false ? false : true;
        this.objMap = {};//对象映射
        this.choiceCallback = params.choiceCallback;//选项选择回调函数
        this.clearCallback = params.clearCallback;//清空选择回调函数
        this.ele.attr('autocomplete', 'off');//关闭浏览器自带的提示
        var _this = this;
        //绑定联想提示
        this.ele.bind('input propertychange focus', function () {
            _this.showClearBtn();
            _this.dreamSearch();
        });
        //关闭联想提示
        this.ele.bind('blur', function () {
            //延时关闭,避免因为关闭过快导致选择无法选中选项
            window.setTimeout(function () {
                _this.closePrompt();
            }, 100);
        });
    },
    matchOption: function () {//选项匹配
        var key = this.key;//输入框的文字
        this.optionData = [];//重置选项
        if (key) {
            for (var i = 0; i < this.data.length; i++) {
                var item = this.data[i];
                var matchField = this.matchField;//用于匹配的字段
                for (var j = 0; j < matchField.length; j++) {
                    if (typeof item[matchField[j]] != 'undefined' && item[matchField[j]].indexOf(key) > -1) {//关键字匹配
                        //console.info('匹配成功---匹配字段='+matchField[j]+',匹配项='+item[matchField[j]]);
                        this.optionData.push(item);
                        break;
                    }
                }
            }
        } else {
            this.optionData = this.data;
        }
        return this.optionData;
    },
    choice: function (id) {
        //console.info("选中了---"+id+"--"+JSON.stringify(this.objMap[id]));
        var item = this.objMap[id];//被选中的数据项
        this.checkedItem = item;//记录当前被选中的选项
        this.checkedKey = this.key;
        this.doClearWhenClose = false;//关闭检索框时不执行清除
        if (typeof (this.choiceCallback) == 'function') {
            this.choiceCallback(item, this);
        } else {
            if (item) {
                //默认填充id和文本值
                if (this.idInp) {
                    $("#" + this.idInp).val(item[this.idField]);
                }
                if (this.textInp) {
                    $("#" + this.textInp).val(item[this.textField]);
                }
            }
        }
    },
    dreamSearch: function () {//联想提示
        this.key = this.ele.val();//输入框的文字
        this.openPrompt();//打开提示框
        if (!this.key || (this.key != this.lastKey)) {//key无效(因为要显示全部)或者key和上次不一样
            //当输入框为不可编辑模式时,如果不存在已选项,或者关键字发生过改动,在关闭检索框时需要清空检索栏(this.lastKey != null是为了去掉第一次获取到焦点时触发的检索,第一次过后lastKey就有几录了,通过判断当前key与lastKey就可以知道检索项有没有变)
            if (!this.editable && (!this.checkedItem || (this.lastKey != null && this.key != this.lastKey))) {
                this.doClearWhenClose = true;
            } else {
                this.doClearWhenClose = false;
            }
            this.lastKey = this.key;//记录搜索值
            var data = this.matchOption();//获取匹配项
            if (data && data.length > 0) {
                $("#" + this.tipsId).html('');
                this.objMap = {};
                for (var i = 0; i < data.length; i++) {
                    var item = data[i];
                    this.objMap[item[this.idField]] = item;
                    $("#" + this.tipsId).append('<div class="jo-tips-item" onclick="JoInput(\'' + this.id + '\').choice(\'' + item[this.idField] + '\')">' + item[this.textField] + '</div>');
                }
            } else {
                if (this.matchFailTips) {
                    $("#" + this.tipsId).html('<div class="jo-tips-item">' + this.matchFailTips + '</div>');
                } else {
                    $("#" + this.tipsId).html('');
                }
            }
        }
    },
    openPrompt: function () {//打开提示
        //计算选择框显示的位置
        var inpXY = this.ele.position();
        var x = inpXY.left;
        var y = inpXY.top + this.ele.outerHeight();
        var w = this.ele.outerWidth();//宽度
        var _html = '<div id="' + this.tipsId + '" class="jo-search-tips" style="left:' + x + 'px;top:' + y + 'px;width:' + w + 'px;"></div>';
        if ($("#" + this.tipsId).length == 0) {
            this.ele.after(_html);
        }
    },
    closePrompt: function () {//关闭提示
        $("#" + this.tipsId).remove();
        if (this.doClearWhenClose) {
            this.ele.val('');
            this.clear();
        }
        this.lastKey = null;//关闭后重新获得焦点时保证能出东西
    },
    showClearBtn: function () {//显示清空按钮
        var inpXY = this.ele.position();
        var w = 25;//宽度
        var h = this.ele.outerHeight();//高度
        var x = inpXY.left + this.ele.outerWidth() - w - 3;
        var y = inpXY.top;
        var _html = '<div id="' + this.tipsId + '_clear" class="jo-tips-input-clear" onclick="JoInput(\'' + this.id + '\').clear()" style="left:' + x + 'px;top:' + y + 'px;width:' + w + 'px;height:' + h + 'px;line-height:' + h + 'px;">×</div>';
        if ($("#" + this.tipsId + "_clear").length == 0) {
            this.ele.after(_html);
        }
    },
    hideClearBtn: function () {
        $("#" + this.tipsId + "_clear").remove();
    },
    clear: function () {
        this.checkedItem = null;//清空当前选项
        if (typeof (this.clearCallback) == 'function') {
            this.clearCallback(this);
        } else {
            //默认清空id和文本输入框的内容
            if (this.idInp) {
                $("#" + this.idInp).val('');
            }
            if (this.textInp) {
                $("#" + this.textInp).val('');
            }
        }
    }
};

(function () {
    /**
     * [Func] jo组件统一格式化ui入口,组件默认采用同步方式进行格式化,避免因为异步带来的渲染顺序问题
     */
    jo.formatUI = function () {
        //console.log("jo组件格式化...");
        //格式化下拉列表
        $(".joSelect").each(function (idx, ele) {
            new JoSelect(ele).init();
        });
    };
})();
//jo组件相关初始化结束